Copy: Support class copying
#KT-8180 Fixed #KT-9054 Fixed
This commit is contained in:
@@ -115,6 +115,7 @@ import org.jetbrains.kotlin.idea.parameterInfo.AbstractParameterInfoTest
|
||||
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.inline.AbstractInlineTest
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractExtractionTest
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.AbstractMoveTest
|
||||
@@ -745,6 +746,10 @@ fun main(args: Array<String>) {
|
||||
model("refactoring/move", extension = "test", singleClass = true)
|
||||
}
|
||||
|
||||
testClass<AbstractCopyTest> {
|
||||
model("refactoring/copy", extension = "test", singleClass = true)
|
||||
}
|
||||
|
||||
testClass<AbstractMultiModuleMoveTest> {
|
||||
model("refactoring/moveMultiModule", extension = "test", singleClass = true)
|
||||
}
|
||||
|
||||
@@ -396,7 +396,14 @@
|
||||
<refactoring.moveInnerClassUsagesHandler
|
||||
implementationClass="org.jetbrains.kotlin.idea.refactoring.move.MoveJavaInnerClassKotlinUsagesHandler"
|
||||
language="kotlin" />
|
||||
<refactoring.copyHandler implementation="org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinFileHandler" order="first" />
|
||||
<refactoring.copyHandler
|
||||
id="kotlinClass"
|
||||
implementation="org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinClassHandler"
|
||||
order="first" />
|
||||
<refactoring.copyHandler
|
||||
id="kotlinFile"
|
||||
implementation="org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinFileHandler"
|
||||
order="after kotlinClass" />
|
||||
<refactoring.changeSignatureUsageProcessor
|
||||
implementation="org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureUsageProcessor"
|
||||
order="after javaProcessor" />
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.openapi.project.Project
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.refactoring.HelpID
|
||||
import com.intellij.refactoring.MoveDestination
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.RecentsManager
|
||||
import com.intellij.ui.ReferenceEditorComboWithBrowseButton
|
||||
import com.intellij.usageView.UsageViewUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import com.intellij.util.ui.UIUtil
|
||||
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.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Font
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
|
||||
// Based on com.intellij.refactoring.copy.CopyClassDialog
|
||||
class CopyKotlinClassDialog(
|
||||
klass: KtClassOrObject,
|
||||
private val defaultTargetDirectory: PsiDirectory?,
|
||||
private val project: Project
|
||||
) : DialogWrapper(project, true) {
|
||||
private val informationLabel = JLabel()
|
||||
private val classNameField = EditorTextField("")
|
||||
private val packageLabel = JLabel()
|
||||
private lateinit var packageNameField: ReferenceEditorComboWithBrowseButton
|
||||
private val openInEditorCheckBox = CopyFilesOrDirectoriesDialog.createOpenInEditorCB()
|
||||
private val destinationComboBox = object : DestinationFolderComboBox() {
|
||||
override fun getTargetPackage() = packageNameField.text.trim()
|
||||
override fun reportBaseInTestSelectionInSource() = true
|
||||
}
|
||||
|
||||
private val originalFile = klass.containingFile
|
||||
|
||||
var targetDirectory: MoveDestination? = null
|
||||
private set
|
||||
|
||||
init {
|
||||
informationLabel.text = RefactoringBundle.message("copy.class.copy.0.1", UsageViewUtil.getType(klass), UsageViewUtil.getLongName(klass))
|
||||
informationLabel.font = informationLabel.font.deriveFont(Font.BOLD)
|
||||
|
||||
init()
|
||||
|
||||
destinationComboBox.setData(
|
||||
project,
|
||||
defaultTargetDirectory,
|
||||
Pass { setErrorText(it, destinationComboBox) },
|
||||
packageNameField.childComponent
|
||||
)
|
||||
classNameField.text = UsageViewUtil.getShortName(klass)
|
||||
classNameField.selectAll()
|
||||
}
|
||||
|
||||
override fun getPreferredFocusedComponent() = classNameField
|
||||
|
||||
override fun createCenterPanel() = JPanel(BorderLayout())
|
||||
|
||||
override fun createNorthPanel(): JComponent? {
|
||||
val qualifiedName = qualifiedName
|
||||
packageNameField = PackageNameReferenceEditorCombo(qualifiedName, project, RECENTS_KEY, RefactoringBundle.message("choose.destination.package"))
|
||||
packageNameField.setTextFieldPreferredWidth(Math.max(qualifiedName.length + 5, 40))
|
||||
packageLabel.text = RefactoringBundle.message("destination.package")
|
||||
packageLabel.labelFor = packageNameField
|
||||
|
||||
val label = JLabel(RefactoringBundle.message("target.destination.folder"))
|
||||
val isMultipleSourceRoots = JavaProjectRootsUtil.getSuitableDestinationSourceRoots(project).size > 1
|
||||
destinationComboBox.isVisible = isMultipleSourceRoots
|
||||
label.isVisible = isMultipleSourceRoots
|
||||
label.labelFor = destinationComboBox
|
||||
|
||||
val panel = JPanel(BorderLayout())
|
||||
panel.add(openInEditorCheckBox, BorderLayout.EAST)
|
||||
return FormBuilder.createFormBuilder()
|
||||
.addComponent(informationLabel)
|
||||
.addLabeledComponent(RefactoringBundle.message("copy.files.new.name.label"), classNameField, UIUtil.LARGE_VGAP)
|
||||
.addLabeledComponent(packageLabel, packageNameField)
|
||||
.addLabeledComponent(label, destinationComboBox)
|
||||
.addComponent(panel)
|
||||
.panel
|
||||
}
|
||||
|
||||
private val qualifiedName: String
|
||||
get() = defaultTargetDirectory?.getPackage()?.qualifiedName ?: ""
|
||||
|
||||
val className: String?
|
||||
get() = classNameField.text
|
||||
|
||||
val openInEditor: Boolean
|
||||
get() = openInEditorCheckBox.isSelected
|
||||
|
||||
private fun checkForErrors(): String? {
|
||||
val packageName = packageNameField.text
|
||||
val className = className
|
||||
|
||||
val manager = PsiManager.getInstance(project)
|
||||
|
||||
if (packageName.isNotEmpty() && !FqNameUnsafe(packageName).hasIdentifiersOnly()) {
|
||||
return RefactoringBundle.message("invalid.target.package.name.specified")
|
||||
}
|
||||
|
||||
if (className.isNullOrEmpty()) {
|
||||
return RefactoringBundle.message("no.class.name.specified")
|
||||
}
|
||||
|
||||
try {
|
||||
targetDirectory = destinationComboBox.selectDirectory(PackageWrapper(manager, packageName), false)
|
||||
}
|
||||
catch (e: IncorrectOperationException) {
|
||||
return e.message
|
||||
}
|
||||
|
||||
targetDirectory?.getTargetIfExists(defaultTargetDirectory)?.let {
|
||||
val targetFileName = className + "." + originalFile.virtualFile.extension
|
||||
if (it.findFile(targetFileName) == originalFile) {
|
||||
return "Can't copy class to the containing file"
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun doOKAction() {
|
||||
val packageName = packageNameField.text
|
||||
|
||||
val errorString = checkForErrors()
|
||||
if (errorString != null) {
|
||||
if (errorString.isNotEmpty()) {
|
||||
Messages.showMessageDialog(project, errorString, RefactoringBundle.message("error.title"), Messages.getErrorIcon())
|
||||
}
|
||||
classNameField.requestFocusInWindow()
|
||||
return
|
||||
}
|
||||
|
||||
RecentsManager.getInstance(project).registerRecentEntry(RECENTS_KEY, packageName)
|
||||
CopyFilesOrDirectoriesDialog.saveOpenInEditorState(openInEditorCheckBox.isSelected)
|
||||
|
||||
super.doOKAction()
|
||||
}
|
||||
|
||||
override fun getHelpId() = HelpID.COPY_CLASS
|
||||
|
||||
companion object {
|
||||
@NonNls private val RECENTS_KEY = "CopyKotlinClassDialog.RECENTS_KEY"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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.ide.util.EditorHelper
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
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.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.MoveDestination
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.copy.CopyHandlerDelegateBase
|
||||
import com.intellij.refactoring.rename.RenameProcessor
|
||||
import com.intellij.refactoring.util.MoveRenameUsageInfo
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.*
|
||||
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.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
|
||||
class CopyKotlinClassHandler : CopyHandlerDelegateBase() {
|
||||
companion object {
|
||||
@set:TestOnly
|
||||
var Project.newName: String? by UserDataProperty(Key.create("NEW_NAME"))
|
||||
|
||||
private fun PsiElement.getTopLevelClass(): KtClassOrObject? {
|
||||
val classOrFile = parentsWithSelf.firstOrNull { it is KtFile || (it is KtClassOrObject && it.isTopLevel()) }
|
||||
return when (classOrFile) {
|
||||
is KtFile -> classOrFile.declarations.singleOrNull() as? KtClassOrObject
|
||||
is KtClassOrObject -> classOrFile
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getClassToCopy(elements: Array<out PsiElement>) = elements.singleOrNull()?.getTopLevelClass()
|
||||
}
|
||||
|
||||
override fun canCopy(elements: Array<out PsiElement>, fromUpdate: Boolean) = getClassToCopy(elements) != null
|
||||
|
||||
enum class ExistingFilePolicy {
|
||||
APPEND, OVERWRITE, SKIP
|
||||
}
|
||||
|
||||
private fun getOrCreateTargetFile(
|
||||
originalFile: KtFile,
|
||||
targetDirectory: PsiDirectory,
|
||||
targetFileName: String,
|
||||
commandName: String
|
||||
): KtFile? {
|
||||
val existingFile = targetDirectory.findFile(targetFileName)
|
||||
if (existingFile == originalFile) return null
|
||||
if (existingFile != null) {
|
||||
val policy = getFilePolicy(existingFile, targetFileName, targetDirectory, commandName)
|
||||
when (policy) {
|
||||
ExistingFilePolicy.APPEND -> {}
|
||||
ExistingFilePolicy.OVERWRITE -> runWriteAction { existingFile.delete() }
|
||||
ExistingFilePolicy.SKIP -> return null
|
||||
}
|
||||
}
|
||||
return runWriteAction {
|
||||
if (existingFile != null && existingFile.isValid) {
|
||||
existingFile as KtFile
|
||||
} else {
|
||||
createKotlinFile(targetFileName, targetDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFilePolicy(
|
||||
existingFile: PsiFile?,
|
||||
targetFileName: String,
|
||||
targetDirectory: PsiDirectory,
|
||||
commandName: String
|
||||
): ExistingFilePolicy {
|
||||
val isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode
|
||||
return if (existingFile !is KtFile) {
|
||||
if (isUnitTestMode) return ExistingFilePolicy.OVERWRITE
|
||||
|
||||
val answer = Messages.showOkCancelDialog(
|
||||
"File $targetFileName already exists in ${targetDirectory.virtualFile.path}",
|
||||
commandName,
|
||||
"Overwrite",
|
||||
"Cancel",
|
||||
Messages.getQuestionIcon()
|
||||
)
|
||||
if (answer == Messages.OK) ExistingFilePolicy.OVERWRITE else ExistingFilePolicy.SKIP
|
||||
}
|
||||
else {
|
||||
if (isUnitTestMode) return ExistingFilePolicy.APPEND
|
||||
|
||||
val answer = Messages.showYesNoCancelDialog(
|
||||
"File $targetFileName already exists in ${targetDirectory.virtualFile.path}",
|
||||
commandName,
|
||||
"Append",
|
||||
"Overwrite",
|
||||
"Cancel",
|
||||
Messages.getQuestionIcon()
|
||||
)
|
||||
when (answer) {
|
||||
Messages.YES -> ExistingFilePolicy.APPEND
|
||||
Messages.NO -> ExistingFilePolicy.OVERWRITE
|
||||
else -> ExistingFilePolicy.SKIP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun doCopy(elements: Array<out PsiElement>, defaultTargetDirectory: PsiDirectory?) {
|
||||
val classToCopy = getClassToCopy(elements) ?: return
|
||||
|
||||
val originalFile = classToCopy.containingKtFile
|
||||
val initialTargetDirectory = defaultTargetDirectory ?: originalFile.containingDirectory ?: return
|
||||
|
||||
val project = initialTargetDirectory.project
|
||||
|
||||
if (ProjectRootManager.getInstance(project).fileIndex.getSourceRootForFile(initialTargetDirectory.virtualFile) == null) return
|
||||
|
||||
val commandName = RefactoringBundle.message("copy.handler.copy.class")
|
||||
|
||||
var openInEditor = false
|
||||
var newClassName: String? = classToCopy.name
|
||||
var moveDestination: MoveDestination? = null
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
val dialog = CopyKotlinClassDialog(classToCopy, initialTargetDirectory, project)
|
||||
dialog.title = commandName
|
||||
if (!dialog.showAndGet()) return
|
||||
|
||||
openInEditor = dialog.openInEditor
|
||||
newClassName = dialog.className
|
||||
moveDestination = dialog.targetDirectory ?: return
|
||||
}
|
||||
else {
|
||||
project.newName?.let { newClassName = it }
|
||||
}
|
||||
|
||||
if (newClassName.isNullOrEmpty()) return
|
||||
|
||||
val internalUsages = runReadAction {
|
||||
val targetPackageName = moveDestination?.targetPackage?.qualifiedName
|
||||
?: initialTargetDirectory.getPackage()?.qualifiedName
|
||||
?: ""
|
||||
val changeInfo = ContainerChangeInfo(
|
||||
ContainerInfo.Package(originalFile.packageFqName),
|
||||
ContainerInfo.Package(FqName(targetPackageName))
|
||||
)
|
||||
classToCopy
|
||||
.getInternalReferencesToUpdateOnPackageNameChange(changeInfo)
|
||||
.filter {
|
||||
val referencedElement = (it as? MoveRenameUsageInfo)?.referencedElement
|
||||
referencedElement == null || !classToCopy.isAncestor(referencedElement)
|
||||
}
|
||||
}
|
||||
markInternalUsages(internalUsages)
|
||||
|
||||
val restoredInternalUsages = ArrayList<UsageInfo>()
|
||||
|
||||
project.executeCommand(commandName) {
|
||||
try {
|
||||
val targetDirectory = runWriteAction { moveDestination?.getTargetDirectory(initialTargetDirectory) ?: initialTargetDirectory }
|
||||
val targetFileName = newClassName + "." + originalFile.virtualFile.extension
|
||||
|
||||
val targetFile = getOrCreateTargetFile(originalFile, targetDirectory, targetFileName, commandName) ?: return@executeCommand
|
||||
|
||||
val newClass = runWriteAction {
|
||||
val newClass = targetFile.add(classToCopy.copy()) as KtClassOrObject
|
||||
val oldToNewElementsMapping: Map<PsiElement, PsiElement> = mapOf(classToCopy to newClass)
|
||||
restoredInternalUsages += restoreInternalUsages(newClass, oldToNewElementsMapping, true)
|
||||
postProcessMoveUsages(restoredInternalUsages, oldToNewElementsMapping)
|
||||
performDelayedRefactoringRequests(project)
|
||||
|
||||
newClass
|
||||
}
|
||||
|
||||
RenameProcessor(project, newClass, newClassName!!.quoteIfNeeded(), false, false).run()
|
||||
|
||||
if (openInEditor) {
|
||||
EditorHelper.openFilesInEditor(arrayOf(targetFile))
|
||||
}
|
||||
}
|
||||
catch (e: IncorrectOperationException) {
|
||||
Messages.showMessageDialog(project, e.message, RefactoringBundle.message("error.title"), Messages.getErrorIcon())
|
||||
}
|
||||
finally {
|
||||
cleanUpInternalUsages(internalUsages + restoredInternalUsages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun doClone(element: PsiElement) {
|
||||
|
||||
}
|
||||
}
|
||||
+3
-19
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations
|
||||
|
||||
import com.intellij.ide.util.EditorHelper
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -30,7 +29,6 @@ import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.MoveMultipleElementsViewDescriptor
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassHandler
|
||||
import com.intellij.refactoring.rename.RenameUtil
|
||||
import com.intellij.refactoring.util.MoveRenameUsageInfo
|
||||
import com.intellij.refactoring.util.NonCodeUsageInfo
|
||||
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||
import com.intellij.refactoring.util.TextOccurrencesUtil
|
||||
@@ -51,7 +49,6 @@ import org.jetbrains.kotlin.idea.refactoring.move.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinClassHandler
|
||||
import org.jetbrains.kotlin.idea.search.projectScope
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
@@ -83,8 +80,6 @@ interface Mover : (KtNamedDeclaration, KtElement) -> KtNamedDeclaration {
|
||||
}
|
||||
}
|
||||
|
||||
internal var KtSimpleNameExpression.internalUsageInfo: UsageInfo? by CopyableUserDataProperty(Key.create("INTERNAL_USAGE_INFO"))
|
||||
|
||||
class MoveDeclarationsDescriptor @JvmOverloads constructor(
|
||||
val project: Project,
|
||||
val elementsToMove: Collection<KtNamedDeclaration>,
|
||||
@@ -245,7 +240,7 @@ class MoveKotlinDeclarationsProcessor(
|
||||
val (oldInternalUsages, externalUsages) = usages.partition { it is KotlinMoveUsage && it.isInternal }
|
||||
val newInternalUsages = ArrayList<UsageInfo>()
|
||||
|
||||
oldInternalUsages.forEach { (it.element as? KtSimpleNameExpression)?.internalUsageInfo = it }
|
||||
markInternalUsages(oldInternalUsages)
|
||||
|
||||
val usagesToProcess = ArrayList(externalUsages)
|
||||
|
||||
@@ -281,16 +276,7 @@ class MoveKotlinDeclarationsProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
newDeclarations.forEach { newDeclaration ->
|
||||
newInternalUsages += newDeclaration.collectDescendantsOfType<KtSimpleNameExpression>().mapNotNull {
|
||||
val usageInfo = it.internalUsageInfo
|
||||
if (usageInfo?.element != null) return@mapNotNull usageInfo
|
||||
val referencedElement = (usageInfo as? MoveRenameUsageInfo)?.referencedElement ?: return@mapNotNull null
|
||||
val newReferencedElement = mapToNewOrThis(referencedElement, oldToNewElementsMapping)
|
||||
if (!newReferencedElement.isValid) return@mapNotNull null
|
||||
(usageInfo as? KotlinMoveUsage)?.refresh(it, newReferencedElement)
|
||||
}
|
||||
}
|
||||
newDeclarations.forEach { newInternalUsages += restoreInternalUsages(it, oldToNewElementsMapping) }
|
||||
|
||||
usagesToProcess += newInternalUsages
|
||||
|
||||
@@ -301,9 +287,7 @@ class MoveKotlinDeclarationsProcessor(
|
||||
RefactoringUIUtil.processIncorrectOperation(myProject, e)
|
||||
}
|
||||
finally {
|
||||
(newInternalUsages + oldInternalUsages).forEach {
|
||||
(it.element as? KtSimpleNameExpression)?.internalUsageInfo = null
|
||||
}
|
||||
cleanUpInternalUsages(newInternalUsages + oldInternalUsages)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,31 @@ fun KtElement.processInternalReferencesToUpdateOnPackageNameChange(
|
||||
}
|
||||
}
|
||||
|
||||
internal var KtSimpleNameExpression.internalUsageInfo: UsageInfo? by CopyableUserDataProperty(Key.create("INTERNAL_USAGE_INFO"))
|
||||
|
||||
internal fun markInternalUsages(usages: Collection<UsageInfo>) {
|
||||
usages.forEach { (it.element as? KtSimpleNameExpression)?.internalUsageInfo = it }
|
||||
}
|
||||
|
||||
internal fun restoreInternalUsages(
|
||||
scope: KtElement,
|
||||
oldToNewElementsMapping: Map<PsiElement, PsiElement>,
|
||||
forcedRestore: Boolean = false
|
||||
): List<UsageInfo> {
|
||||
return scope.collectDescendantsOfType<KtSimpleNameExpression>().mapNotNull {
|
||||
val usageInfo = it.internalUsageInfo
|
||||
if (!forcedRestore && usageInfo?.element != null) return@mapNotNull usageInfo
|
||||
val referencedElement = (usageInfo as? MoveRenameUsageInfo)?.referencedElement ?: return@mapNotNull null
|
||||
val newReferencedElement = mapToNewOrThis(referencedElement, oldToNewElementsMapping)
|
||||
if (!newReferencedElement.isValid) return@mapNotNull null
|
||||
(usageInfo as? KotlinMoveUsage)?.refresh(it, newReferencedElement)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun cleanUpInternalUsages(usages: Collection<UsageInfo>) {
|
||||
usages.forEach { (it.element as? KtSimpleNameExpression)?.internalUsageInfo = null }
|
||||
}
|
||||
|
||||
class ImplicitCompanionAsDispatchReceiverUsageInfo(
|
||||
callee: KtSimpleNameExpression,
|
||||
val companionDescriptor: ClassDescriptor
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package bar
|
||||
|
||||
import foo.B
|
||||
|
||||
class A {
|
||||
val a: A = A()
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -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 @@
|
||||
package foo
|
||||
|
||||
class A {
|
||||
val a: <caret>A = A()
|
||||
val b: B = B()
|
||||
}
|
||||
|
||||
class B {
|
||||
val a: A = A()
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package bar
|
||||
|
||||
import foo.B
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
|
||||
class A {
|
||||
val a: A = A()
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -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,5 @@
|
||||
package bar
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package bar
|
||||
|
||||
import foo.B
|
||||
|
||||
class A {
|
||||
val a: A = A()
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -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 @@
|
||||
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,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package bar
|
||||
|
||||
class A {
|
||||
fun test() {
|
||||
A.testCompanion()
|
||||
testCompanion()
|
||||
Companion.testCompanion()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun testCompanion() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package foo
|
||||
|
||||
class A {
|
||||
fun test() {
|
||||
A.testCompanion()
|
||||
testCompanion()
|
||||
Companion.testCompanion()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun testCompanion() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package foo
|
||||
|
||||
class <caret>A {
|
||||
fun test() {
|
||||
A.testCompanion()
|
||||
testCompanion()
|
||||
Companion.testCompanion()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun testCompanion() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package bar
|
||||
|
||||
import foo.B
|
||||
|
||||
class X {
|
||||
val a: X = X()
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -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 @@
|
||||
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,5 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar",
|
||||
"newName": "X"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package bar
|
||||
|
||||
class A {
|
||||
class B
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package foo
|
||||
|
||||
class A {
|
||||
class B
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package foo
|
||||
|
||||
class A {
|
||||
class <caret>B
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package bar
|
||||
|
||||
import foo.B
|
||||
|
||||
object A {
|
||||
val a: A = A
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package foo
|
||||
|
||||
object A {
|
||||
val a: A = A
|
||||
val b: B = B()
|
||||
}
|
||||
|
||||
class B {
|
||||
val a: A = A
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package foo
|
||||
|
||||
object <caret>A {
|
||||
val a: A = A
|
||||
val b: B = B()
|
||||
}
|
||||
|
||||
class B {
|
||||
val a: A = A
|
||||
val b: B = B()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package bar
|
||||
|
||||
class A
|
||||
@@ -0,0 +1,3 @@
|
||||
package foo
|
||||
|
||||
class A
|
||||
@@ -0,0 +1,3 @@
|
||||
package foo
|
||||
|
||||
class A
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "foo/test.kt",
|
||||
"targetPackage": "bar"
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.intellij.codeInsight.TargetElementUtil
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveAction
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.runMoveRefactoring
|
||||
import org.jetbrains.kotlin.idea.refactoring.rename.loadTestConfiguration
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.test.extractMultipleMarkerOffsets
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractMultifileRefactoringTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
interface RefactoringAction {
|
||||
fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject)
|
||||
}
|
||||
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
if (KotlinTestUtils.isAllFilesPresentTest(getTestName(false))) return super.getProjectDescriptor()
|
||||
|
||||
val testConfigurationFile = File(super.getTestDataPath(), fileName())
|
||||
val config = loadTestConfiguration(testConfigurationFile)
|
||||
val withRuntime = config["withRuntime"]?.asBoolean ?: false
|
||||
if (withRuntime) {
|
||||
return KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
return KotlinLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
|
||||
protected abstract fun runRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project)
|
||||
|
||||
protected fun doTest(path: String) {
|
||||
val testFile = File(path)
|
||||
val config = JsonParser().parse(FileUtil.loadFile(testFile, true)) as JsonObject
|
||||
|
||||
doTestCommittingDocuments(testFile) { rootDir ->
|
||||
runRefactoring(path, config, rootDir, project)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getTestDirName(lowercaseFirstLetter : Boolean) : String {
|
||||
val testName = getTestName(lowercaseFirstLetter)
|
||||
val endIndex = testName.lastIndexOf('_')
|
||||
if (endIndex < 0) return testName
|
||||
return testName.substring(0, endIndex).replace('_', '/')
|
||||
}
|
||||
|
||||
override fun getTestDataPath() = super.getTestDataPath() + "/" + getTestDirName(true)
|
||||
|
||||
protected fun doTestCommittingDocuments(testFile: File, action: (VirtualFile) -> Unit) {
|
||||
val beforeVFile = myFixture.copyDirectoryToProject("before", "")
|
||||
PsiDocumentManager.getInstance(myFixture.project).commitAllDocuments()
|
||||
|
||||
val afterDir = File(testFile.parentFile, "after")
|
||||
val afterVFile = LocalFileSystem.getInstance().findFileByIoFile(afterDir)?.apply {
|
||||
UsefulTestCase.refreshRecursively(this)
|
||||
}
|
||||
|
||||
action(beforeVFile)
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
PlatformTestUtil.assertDirectoriesEqual(afterVFile, beforeVFile)
|
||||
}
|
||||
}
|
||||
|
||||
fun runRefactoringTest(
|
||||
path: String,
|
||||
config: JsonObject,
|
||||
rootDir: VirtualFile,
|
||||
project: Project,
|
||||
action: AbstractMultifileRefactoringTest.RefactoringAction
|
||||
) {
|
||||
val testDir = path.substring(0, path.lastIndexOf("/"))
|
||||
val mainFilePath = config.getNullableString("mainFile") ?: config.getAsJsonArray("filesToMove").first().asString
|
||||
|
||||
val conflictFile = File(testDir + "/conflicts.txt")
|
||||
|
||||
val mainFile = rootDir.findFileByRelativePath(mainFilePath)!!
|
||||
val mainPsiFile = PsiManager.getInstance(project).findFile(mainFile)!!
|
||||
val document = FileDocumentManager.getInstance().getDocument(mainFile)!!
|
||||
val editor = EditorFactory.getInstance()!!.createEditor(document, project)!!
|
||||
|
||||
val caretOffsets = document.extractMultipleMarkerOffsets(project)
|
||||
val elementsAtCaret = caretOffsets.map {
|
||||
TargetElementUtil.getInstance().findTargetElement(
|
||||
editor,
|
||||
TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED or TargetElementUtil.ELEMENT_NAME_ACCEPTED,
|
||||
it
|
||||
)!!
|
||||
}
|
||||
|
||||
try {
|
||||
action.runRefactoring(rootDir, mainPsiFile, elementsAtCaret, config)
|
||||
|
||||
assert(!conflictFile.exists())
|
||||
}
|
||||
catch(e: BaseRefactoringProcessor.ConflictsInTestsException) {
|
||||
KotlinTestUtils.assertEqualsToFile(conflictFile, e.messages.distinct().sorted().joinToString("\n"))
|
||||
|
||||
BaseRefactoringProcessor.ConflictsInTestsException.setTestIgnore(true)
|
||||
|
||||
// Run refactoring again with ConflictsInTestsException suppressed
|
||||
action.runRefactoring(rootDir, mainPsiFile, elementsAtCaret, config)
|
||||
}
|
||||
finally {
|
||||
BaseRefactoringProcessor.ConflictsInTestsException.setTestIgnore(false)
|
||||
|
||||
EditorFactory.getInstance()!!.releaseEditor(editor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.google.gson.JsonObject
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import com.intellij.refactoring.copy.CopyHandler
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||
import org.jetbrains.kotlin.idea.refactoring.AbstractMultifileRefactoringTest
|
||||
import org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinClassHandler.Companion.newName
|
||||
import org.jetbrains.kotlin.idea.refactoring.runRefactoringTest
|
||||
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<PsiElement>, config: JsonObject) {
|
||||
val elementsToCopy = elementsAtCaret.ifEmpty { listOf(mainFile) }.toTypedArray()
|
||||
assert(CopyHandler.canCopy(elementsToCopy))
|
||||
|
||||
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(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
||||
runRefactoringTest(path, config, rootDir, project, this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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/copy")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class CopyTestGenerated extends AbstractCopyTest {
|
||||
public void testAllFilesPresentInCopy() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/copy"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY);
|
||||
}
|
||||
|
||||
@TestMetadata("copyClassCaretInside/copyClassCaretInside.test")
|
||||
public void testCopyClassCaretInside_CopyClassCaretInside() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyClassCaretInside/copyClassCaretInside.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copyClassToExistingFile/copyClassToExistingFile.test")
|
||||
public void testCopyClassToExistingFile_CopyClassToExistingFile() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyClassToExistingFile/copyClassToExistingFile.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copyClassToNewFile/copyClassToNewFile.test")
|
||||
public void testCopyClassToNewFile_CopyClassToNewFile() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyClassToNewFile/copyClassToNewFile.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copyClassWithCompanionRefs/copyClassWithCompanionRefs.test")
|
||||
public void testCopyClassWithCompanionRefs_CopyClassWithCompanionRefs() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyClassWithCompanionRefs/copyClassWithCompanionRefs.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copyClassWithRename/copyClassWithRename.test")
|
||||
public void testCopyClassWithRename_CopyClassWithRename() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyClassWithRename/copyClassWithRename.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copyNestedClass/copyNestedClass.test")
|
||||
public void testCopyNestedClass_CopyNestedClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyNestedClass/copyNestedClass.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copyObject/copyObject.test")
|
||||
public void testCopyObject_CopyObject() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copyObject/copyObject.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("copySingleClassFile/copySingleClassFile.test")
|
||||
public void testCopySingleClassFile_CopySingleClassFile() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copy/copySingleClassFile/copySingleClassFile.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,11 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.move
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.intellij.codeInsight.TargetElementUtil
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.MoveDestination
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import com.intellij.refactoring.move.MoveHandler
|
||||
@@ -37,122 +30,33 @@ import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectori
|
||||
import com.intellij.refactoring.move.moveInner.MoveInnerProcessor
|
||||
import com.intellij.refactoring.move.moveMembers.MockMoveMembersOptions
|
||||
import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.util.ActionRunner
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.changePackage.KotlinChangePackageRefactoring
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareDelegatingMoveDestination
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.rename.loadTestConfiguration
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiDirectory
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
||||
import org.jetbrains.kotlin.idea.search.allScope
|
||||
import org.jetbrains.kotlin.idea.search.projectScope
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
|
||||
import org.jetbrains.kotlin.idea.test.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractMoveTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
if (KotlinTestUtils.isAllFilesPresentTest(getTestName(false))) return super.getProjectDescriptor()
|
||||
|
||||
val testConfigurationFile = File(super.getTestDataPath(), fileName())
|
||||
val config = loadTestConfiguration(testConfigurationFile)
|
||||
val withRuntime = config["withRuntime"]?.asBoolean ?: false
|
||||
if (withRuntime) {
|
||||
return KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
return KotlinLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
|
||||
protected fun doTest(path: String) {
|
||||
val testFile = File(path)
|
||||
val config = JsonParser().parse(FileUtil.loadFile(testFile, true)) as JsonObject
|
||||
|
||||
doTestCommittingDocuments(testFile) { rootDir ->
|
||||
runMoveRefactoring(path, config, rootDir, project)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getTestDirName(lowercaseFirstLetter : Boolean) : String {
|
||||
val testName = getTestName(lowercaseFirstLetter)
|
||||
val endIndex = testName.lastIndexOf('_')
|
||||
if (endIndex < 0) return testName
|
||||
return testName.substring(0, endIndex).replace('_', '/')
|
||||
}
|
||||
|
||||
override fun getTestDataPath() = super.getTestDataPath() + "/" + getTestDirName(true)
|
||||
|
||||
protected fun doTestCommittingDocuments(testFile: File, action: (VirtualFile) -> Unit) {
|
||||
val beforeVFile = myFixture.copyDirectoryToProject("before", "")
|
||||
PsiDocumentManager.getInstance(myFixture.project).commitAllDocuments()
|
||||
|
||||
val afterDir = File(testFile.parentFile, "after")
|
||||
val afterVFile = LocalFileSystem.getInstance().findFileByIoFile(afterDir)?.apply {
|
||||
UsefulTestCase.refreshRecursively(this)
|
||||
}
|
||||
|
||||
action(beforeVFile)
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
PlatformTestUtil.assertDirectoriesEqual(afterVFile, beforeVFile)
|
||||
abstract class AbstractMoveTest : AbstractMultifileRefactoringTest() {
|
||||
override fun runRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
||||
runMoveRefactoring(path, config, rootDir, project)
|
||||
}
|
||||
}
|
||||
|
||||
fun runMoveRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) {
|
||||
val action = MoveAction.valueOf(config.getString("type"))
|
||||
|
||||
val testDir = path.substring(0, path.lastIndexOf("/"))
|
||||
val mainFilePath = config.getNullableString("mainFile") ?: config.getAsJsonArray("filesToMove").first().asString
|
||||
|
||||
val conflictFile = File(testDir + "/conflicts.txt")
|
||||
|
||||
val mainFile = rootDir.findFileByRelativePath(mainFilePath)!!
|
||||
val mainPsiFile = PsiManager.getInstance(project).findFile(mainFile)!!
|
||||
val document = FileDocumentManager.getInstance().getDocument(mainFile)!!
|
||||
val editor = EditorFactory.getInstance()!!.createEditor(document, project)!!
|
||||
|
||||
val caretOffsets = document.extractMultipleMarkerOffsets(project)
|
||||
val elementsAtCaret = caretOffsets.map {
|
||||
TargetElementUtil.getInstance().findTargetElement(
|
||||
editor,
|
||||
TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED or TargetElementUtil.ELEMENT_NAME_ACCEPTED,
|
||||
it
|
||||
)!!
|
||||
}
|
||||
|
||||
try {
|
||||
action.runRefactoring(rootDir, mainPsiFile, elementsAtCaret, config)
|
||||
|
||||
assert(!conflictFile.exists())
|
||||
}
|
||||
catch(e: ConflictsInTestsException) {
|
||||
KotlinTestUtils.assertEqualsToFile(conflictFile, e.messages.distinct().sorted().joinToString("\n"))
|
||||
|
||||
ConflictsInTestsException.setTestIgnore(true)
|
||||
|
||||
// Run refactoring again with ConflictsInTestsException suppressed
|
||||
action.runRefactoring(rootDir, mainPsiFile, elementsAtCaret, config)
|
||||
}
|
||||
finally {
|
||||
ConflictsInTestsException.setTestIgnore(false)
|
||||
|
||||
EditorFactory.getInstance()!!.releaseEditor(editor)
|
||||
}
|
||||
runRefactoringTest(path, config, rootDir, project, MoveAction.valueOf(config.getString("type")))
|
||||
}
|
||||
|
||||
enum class MoveAction {
|
||||
enum class MoveAction : AbstractMultifileRefactoringTest.RefactoringAction {
|
||||
MOVE_MEMBERS {
|
||||
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
|
||||
val members = elementsAtCaret.map { it.getNonStrictParentOfType<PsiMember>()!! }
|
||||
@@ -387,6 +291,4 @@ enum class MoveAction {
|
||||
MoveKotlinDeclarationsProcessor(descriptor).run()
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user