Copy: Report module accessibility conflicts

This commit is contained in:
Alexey Sedunov
2017-05-16 14:35:52 +03:00
parent bfb3b38ebc
commit a80fb6f20f
30 changed files with 365 additions and 50 deletions
@@ -116,6 +116,7 @@ import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixMultiFileTest
import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest
import org.jetbrains.kotlin.idea.refactoring.AbstractNameSuggestionProviderTest import org.jetbrains.kotlin.idea.refactoring.AbstractNameSuggestionProviderTest
import org.jetbrains.kotlin.idea.refactoring.copy.AbstractCopyTest 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.inline.AbstractInlineTest
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractExtractionTest import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractExtractionTest
import org.jetbrains.kotlin.idea.refactoring.move.AbstractMoveTest import org.jetbrains.kotlin.idea.refactoring.move.AbstractMoveTest
@@ -759,6 +760,10 @@ fun main(args: Array<String>) {
model("refactoring/moveMultiModule", extension = "test", singleClass = true) model("refactoring/moveMultiModule", extension = "test", singleClass = true)
} }
testClass<AbstractMultiModuleCopyTest> {
model("refactoring/copyMultiModule", extension = "test", singleClass = true)
}
testClass<AbstractMultiFileIntentionTest> { testClass<AbstractMultiFileIntentionTest> {
model("multiFileIntentions", extension = "test", singleClass = true, filenameStartsLowerCase = true) model("multiFileIntentions", extension = "test", singleClass = true, filenameStartsLowerCase = true)
} }
@@ -22,10 +22,12 @@ import com.intellij.injected.editor.VirtualFileWindow
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.FileIndex import com.intellij.openapi.roots.FileIndex
import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.util.Ref import com.intellij.openapi.util.Ref
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import org.jetbrains.kotlin.idea.KotlinModuleFileType import org.jetbrains.kotlin.idea.KotlinModuleFileType
import org.jetbrains.kotlin.idea.caches.resolve.JsProjectDetector import org.jetbrains.kotlin.idea.caches.resolve.JsProjectDetector
import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager
@@ -40,6 +42,11 @@ fun FileIndex.isInSourceContentWithoutInjected(file: VirtualFile): Boolean {
return file !is VirtualFileWindow && isInSourceContent(file) 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 { object ProjectRootsUtil {
@JvmStatic fun isInContent(project: Project, file: VirtualFile, includeProjectSource: Boolean, @JvmStatic fun isInContent(project: Project, file: VirtualFile, includeProjectSource: Boolean,
includeLibrarySource: Boolean, includeLibraryClasses: Boolean, includeLibrarySource: Boolean, includeLibraryClasses: Boolean,
@@ -15,10 +15,12 @@
*/ */
package org.jetbrains.kotlin.idea.refactoring.copy package org.jetbrains.kotlin.idea.refactoring.copy
import com.intellij.ide.util.DirectoryChooser
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.refactoring.HelpID 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.core.getPackage
import org.jetbrains.kotlin.idea.refactoring.Pass import org.jetbrains.kotlin.idea.refactoring.Pass
import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
import org.jetbrains.kotlin.idea.util.sourceRoot
import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration
import java.awt.BorderLayout import java.awt.BorderLayout
@@ -68,6 +71,9 @@ class CopyKotlinDeclarationDialog(
var targetDirectory: MoveDestination? = null var targetDirectory: MoveDestination? = null
private set private set
val targetSourceRoot: VirtualFile?
get() = ((destinationComboBox.comboBox.selectedItem as? DirectoryChooser.ItemWrapper)?.directory ?: originalFile).sourceRoot
init { init {
informationLabel.text = RefactoringBundle.message("copy.class.copy.0.1", UsageViewUtil.getType(declaration), UsageViewUtil.getLongName(declaration)) informationLabel.text = RefactoringBundle.message("copy.class.copy.0.1", UsageViewUtil.getType(declaration), UsageViewUtil.getLongName(declaration))
informationLabel.font = informationLabel.font.deriveFont(Font.BOLD) informationLabel.font = informationLabel.font.deriveFont(Font.BOLD)
@@ -22,10 +22,12 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiNamedElement
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog
import com.intellij.refactoring.copy.CopyHandlerDelegateBase 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.refactoring.util.MoveRenameUsageInfo
import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageInfo
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import com.intellij.util.containers.MultiMap
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests
import org.jetbrains.kotlin.idea.core.quoteIfNeeded 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.createKotlinFile
import org.jetbrains.kotlin.idea.refactoring.move.* 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.executeCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction 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.name.FqName
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
@@ -55,7 +63,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
@set:TestOnly @set:TestOnly
var Project.newName: String? by UserDataProperty(Key.create("NEW_NAME")) var Project.newName: String? by UserDataProperty(Key.create("NEW_NAME"))
private fun PsiElement.getElementsToCopy(): List<PsiNamedElement> { private fun PsiElement.getElementsToCopy(): List<KtElement> {
val declarationOrFile = parentsWithSelf.firstOrNull { it is KtFile || (it is KtNamedDeclaration && it.parent is KtFile) } val declarationOrFile = parentsWithSelf.firstOrNull { it is KtFile || (it is KtNamedDeclaration && it.parent is KtFile) }
return when (declarationOrFile) { return when (declarationOrFile) {
is KtFile -> declarationOrFile.declarations.filterIsInstance<KtNamedDeclaration>().ifEmpty { listOf(declarationOrFile) } is KtFile -> declarationOrFile.declarations.filterIsInstance<KtNamedDeclaration>().ifEmpty { listOf(declarationOrFile) }
@@ -147,15 +155,16 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
val project = initialTargetDirectory.project val project = initialTargetDirectory.project
if (ProjectRootManager.getInstance(project).fileIndex.getSourceRootForFile(initialTargetDirectory.virtualFile) == null) return
val commandName = "Copy Declarations" val commandName = "Copy Declarations"
var openInEditor = false var openInEditor = false
var newName: String? = singleElementToCopy?.name ?: originalFile.name var newName: String? = singleElementToCopy?.name ?: originalFile.name
var targetDirWrapper: AutocreatingPsiDirectoryWrapper = initialTargetDirectory.toDirectoryWrapper() 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) { if (singleElementToCopy != null && singleElementToCopy is KtNamedDeclaration) {
val dialog = CopyKotlinDeclarationDialog(singleElementToCopy, initialTargetDirectory, project) val dialog = CopyKotlinDeclarationDialog(singleElementToCopy, initialTargetDirectory, project)
dialog.title = commandName dialog.title = commandName
@@ -164,6 +173,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
openInEditor = dialog.openInEditor openInEditor = dialog.openInEditor
newName = dialog.newName ?: singleElementToCopy.name newName = dialog.newName ?: singleElementToCopy.name
targetDirWrapper = dialog.targetDirectory?.toDirectoryWrapper() ?: return targetDirWrapper = dialog.targetDirectory?.toDirectoryWrapper() ?: return
targetSourceRoot = dialog.targetSourceRoot
} }
else { else {
val dialog = CopyFilesOrDirectoriesDialog(arrayOf(originalFile), initialTargetDirectory, project, false) val dialog = CopyFilesOrDirectoriesDialog(arrayOf(originalFile), initialTargetDirectory, project, false)
@@ -171,6 +181,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
openInEditor = dialog.openInEditor() openInEditor = dialog.openInEditor()
newName = dialog.newName newName = dialog.newName
targetDirWrapper = dialog.targetDirectory?.toDirectoryWrapper() ?: return targetDirWrapper = dialog.targetDirectory?.toDirectoryWrapper() ?: return
targetSourceRoot = dialog.targetDirectory?.sourceRoot
} }
} }
else { else {
@@ -185,8 +196,8 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
ContainerInfo.Package(originalFile.packageFqName), ContainerInfo.Package(originalFile.packageFqName),
ContainerInfo.Package(FqName(targetPackageName)) ContainerInfo.Package(FqName(targetPackageName))
) )
elementsToCopy.flatMap { elementToCopy -> elementsToCopy.flatMapTo(LinkedHashSet()) { elementToCopy ->
(elementToCopy as KtElement).getInternalReferencesToUpdateOnPackageNameChange(changeInfo).filter { elementToCopy.getInternalReferencesToUpdateOnPackageNameChange(changeInfo).filter {
val referencedElement = (it as? MoveRenameUsageInfo)?.referencedElement val referencedElement = (it as? MoveRenameUsageInfo)?.referencedElement
referencedElement == null || !elementToCopy.isAncestor(referencedElement) referencedElement == null || !elementToCopy.isAncestor(referencedElement)
} }
@@ -194,51 +205,71 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() {
} }
markInternalUsages(internalUsages) markInternalUsages(internalUsages)
val restoredInternalUsages = ArrayList<UsageInfo>() fun doRefactor() {
val restoredInternalUsages = ArrayList<UsageInfo>()
project.executeCommand(commandName) { project.executeCommand(commandName) {
try { try {
val targetDirectory = runWriteAction { targetDirWrapper.getOrCreateDirectory(initialTargetDirectory) } val targetDirectory = runWriteAction { targetDirWrapper.getOrCreateDirectory(initialTargetDirectory) }
val targetFileName = if (newName?.contains(".") ?: false) newName!! else newName + "." + originalFile.virtualFile.extension val targetFileName = if (newName?.contains(".") ?: false) newName!! else newName + "." + originalFile.virtualFile.extension
val oldToNewElementsMapping = HashMap<PsiElement, PsiElement>() val oldToNewElementsMapping = HashMap<PsiElement, PsiElement>()
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 { runWriteAction {
val newElements = elementsToCopy.map { targetFile.add(it.copy()) as KtNamedDeclaration } for (newElement in oldToNewElementsMapping.values) {
elementsToCopy.zip(newElements).toMap(oldToNewElementsMapping) restoredInternalUsages += restoreInternalUsages(newElement as KtElement, oldToNewElementsMapping, true)
} postProcessMoveUsages(restoredInternalUsages, oldToNewElementsMapping)
} }
runWriteAction { performDelayedRefactoringRequests(project)
for (newElement in oldToNewElementsMapping.values) {
restoredInternalUsages += restoreInternalUsages(newElement as KtElement, oldToNewElementsMapping, true)
postProcessMoveUsages(restoredInternalUsages, oldToNewElementsMapping)
} }
performDelayedRefactoringRequests(project) oldToNewElementsMapping.values.singleOrNull()?.let {
} RenameProcessor(project, it, newName!!.quoteIfNeeded(), false, false).run()
}
oldToNewElementsMapping.values.singleOrNull()?.let { if (openInEditor) {
RenameProcessor(project, it, newName!!.quoteIfNeeded(), false, false).run() EditorHelper.openFilesInEditor(arrayOf(targetFile))
}
} }
catch (e: IncorrectOperationException) {
if (openInEditor) { Messages.showMessageDialog(project, e.message, RefactoringBundle.message("error.title"), Messages.getErrorIcon())
EditorHelper.openFilesInEditor(arrayOf(targetFile)) }
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<PsiElement, String>()
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) { override fun doClone(element: PsiElement) {
@@ -219,7 +219,7 @@ class MoveConflictChecker(
} }
} }
private fun checkModuleConflictsInDeclarations( fun checkModuleConflictsInDeclarations(
internalUsages: MutableSet<UsageInfo>, internalUsages: MutableSet<UsageInfo>,
conflicts: MultiMap<PsiElement, String> conflicts: MultiMap<PsiElement, String>
) { ) {
@@ -327,7 +327,7 @@ class MoveConflictChecker(
} }
} }
private fun checkVisibilityInDeclarations(conflicts: MultiMap<PsiElement, String>) { fun checkVisibilityInDeclarations(conflicts: MultiMap<PsiElement, String>) {
val targetContainer = moveTarget.getContainerDescriptor() ?: return val targetContainer = moveTarget.getContainerDescriptor() ?: return
fun DeclarationDescriptor.targetAwareContainingDescriptor(): DeclarationDescriptor? { fun DeclarationDescriptor.targetAwareContainingDescriptor(): DeclarationDescriptor? {
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -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()
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="A" />
</component>
</module>
@@ -0,0 +1,8 @@
package bar
import foo.B
internal class A {
val a: A = A()
val b: B = B()
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,11 @@
package foo
internal class <caret>A {
val a: A = A()
val b: B = B()
}
internal class B {
val a: A = A()
val b: B = B()
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="A" />
</component>
</module>
@@ -0,0 +1 @@
Class A uses class B which will be inaccessible after move
@@ -0,0 +1,4 @@
{
"mainFile": "A/src/foo/test.kt",
"targetDirectory": "B/src/bar"
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -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()
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,6 @@
package bar
class A {
val a: A = A()
val b: foo.B = foo.B()
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,11 @@
package foo
class <caret>A {
val a: A = A()
val b: B = B()
}
class B {
val a: A = A()
val b: B = B()
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1 @@
Class foo.B, referenced in property A.b, will not be accessible in module B
@@ -0,0 +1,4 @@
{
"mainFile": "A/src/foo/test.kt",
"targetDirectory": "B/src/bar"
}
@@ -29,21 +29,37 @@ import org.jetbrains.kotlin.idea.jsonUtils.getString
import org.jetbrains.kotlin.idea.refactoring.AbstractMultifileRefactoringTest import org.jetbrains.kotlin.idea.refactoring.AbstractMultifileRefactoringTest
import org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinDeclarationsHandler.Companion.newName import org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinDeclarationsHandler.Companion.newName
import org.jetbrains.kotlin.idea.refactoring.runRefactoringTest 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.idea.util.application.runWriteAction
import org.jetbrains.kotlin.utils.ifEmpty import org.jetbrains.kotlin.utils.ifEmpty
abstract class AbstractCopyTest : AbstractMultifileRefactoringTest(), AbstractMultifileRefactoringTest.RefactoringAction { abstract class AbstractCopyTest : AbstractMultifileRefactoringTest() {
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) { companion object : RefactoringAction {
val elementsToCopy = elementsAtCaret.ifEmpty { listOf(mainFile) }.toTypedArray() fun runCopyRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
assert(CopyHandler.canCopy(elementsToCopy)) runRefactoringTest(path, config, rootDir, project, this)
}
val packageWrapper = PackageWrapper(mainFile.manager, config.getString("targetPackage")) override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
project.newName = config.getNullableString("newName") val project = mainFile.project
val targetDirectory = runWriteAction { MultipleRootsMoveDestination(packageWrapper).getTargetDirectory(mainFile) }
CopyHandler.doCopy(elementsToCopy, targetDirectory) 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) { override fun runRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
runRefactoringTest(path, config, rootDir, project, this) runCopyRefactoring(path, config, rootDir, project)
} }
} }
@@ -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)
}
}
}
@@ -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);
}
}