diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 32f5ebd3cca..e82fd6a4c44 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -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) { model("refactoring/move", extension = "test", singleClass = true) } + testClass { + model("refactoring/copy", extension = "test", singleClass = true) + } + testClass { model("refactoring/moveMultiModule", extension = "test", singleClass = true) } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index c7389765a62..1bfb2a32cf2 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -396,7 +396,14 @@ - + + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinClassDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinClassDialog.kt new file mode 100644 index 00000000000..2e7ddd09264 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinClassDialog.kt @@ -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" + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinClassHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinClassHandler.kt new file mode 100644 index 00000000000..36d8309244a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinClassHandler.kt @@ -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) = elements.singleOrNull()?.getTopLevelClass() + } + + override fun canCopy(elements: Array, 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, 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() + + 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 = 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) { + + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt index 99915fd830c..d1657a94031 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt @@ -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, @@ -245,7 +240,7 @@ class MoveKotlinDeclarationsProcessor( val (oldInternalUsages, externalUsages) = usages.partition { it is KotlinMoveUsage && it.isInternal } val newInternalUsages = ArrayList() - 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().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) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 22a3434ccb5..ede78dfcc58 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -181,6 +181,31 @@ fun KtElement.processInternalReferencesToUpdateOnPackageNameChange( } } +internal var KtSimpleNameExpression.internalUsageInfo: UsageInfo? by CopyableUserDataProperty(Key.create("INTERNAL_USAGE_INFO")) + +internal fun markInternalUsages(usages: Collection) { + usages.forEach { (it.element as? KtSimpleNameExpression)?.internalUsageInfo = it } +} + +internal fun restoreInternalUsages( + scope: KtElement, + oldToNewElementsMapping: Map, + forcedRestore: Boolean = false +): List { + return scope.collectDescendantsOfType().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) { + usages.forEach { (it.element as? KtSimpleNameExpression)?.internalUsageInfo = null } +} + class ImplicitCompanionAsDispatchReceiverUsageInfo( callee: KtSimpleNameExpression, val companionDescriptor: ClassDescriptor diff --git a/idea/testData/refactoring/copy/copyClassCaretInside/after/bar/A.kt b/idea/testData/refactoring/copy/copyClassCaretInside/after/bar/A.kt new file mode 100644 index 00000000000..b377d902651 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassCaretInside/after/bar/A.kt @@ -0,0 +1,8 @@ +package bar + +import foo.B + +class A { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassCaretInside/after/foo/test.kt b/idea/testData/refactoring/copy/copyClassCaretInside/after/foo/test.kt new file mode 100644 index 00000000000..c6688a1d8c2 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassCaretInside/after/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() +} diff --git a/idea/testData/refactoring/copy/copyClassCaretInside/before/foo/test.kt b/idea/testData/refactoring/copy/copyClassCaretInside/before/foo/test.kt new file mode 100644 index 00000000000..0698c3acf97 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassCaretInside/before/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() +} diff --git a/idea/testData/refactoring/copy/copyClassCaretInside/copyClassCaretInside.test b/idea/testData/refactoring/copy/copyClassCaretInside/copyClassCaretInside.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassCaretInside/copyClassCaretInside.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/testData/refactoring/copy/copyClassToExistingFile/after/bar/A.kt b/idea/testData/refactoring/copy/copyClassToExistingFile/after/bar/A.kt new file mode 100644 index 00000000000..69ffa551ea8 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToExistingFile/after/bar/A.kt @@ -0,0 +1,12 @@ +package bar + +import foo.B + +fun foo() { + +} + +class A { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassToExistingFile/after/foo/test.kt b/idea/testData/refactoring/copy/copyClassToExistingFile/after/foo/test.kt new file mode 100644 index 00000000000..c6688a1d8c2 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToExistingFile/after/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() +} diff --git a/idea/testData/refactoring/copy/copyClassToExistingFile/before/bar/A.kt b/idea/testData/refactoring/copy/copyClassToExistingFile/before/bar/A.kt new file mode 100644 index 00000000000..0313e287cc1 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToExistingFile/before/bar/A.kt @@ -0,0 +1,5 @@ +package bar + +fun foo() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassToExistingFile/before/foo/test.kt b/idea/testData/refactoring/copy/copyClassToExistingFile/before/foo/test.kt new file mode 100644 index 00000000000..52166ff2636 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToExistingFile/before/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() +} diff --git a/idea/testData/refactoring/copy/copyClassToExistingFile/copyClassToExistingFile.test b/idea/testData/refactoring/copy/copyClassToExistingFile/copyClassToExistingFile.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToExistingFile/copyClassToExistingFile.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/testData/refactoring/copy/copyClassToNewFile/after/bar/A.kt b/idea/testData/refactoring/copy/copyClassToNewFile/after/bar/A.kt new file mode 100644 index 00000000000..b377d902651 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToNewFile/after/bar/A.kt @@ -0,0 +1,8 @@ +package bar + +import foo.B + +class A { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassToNewFile/after/foo/test.kt b/idea/testData/refactoring/copy/copyClassToNewFile/after/foo/test.kt new file mode 100644 index 00000000000..c6688a1d8c2 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToNewFile/after/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() +} diff --git a/idea/testData/refactoring/copy/copyClassToNewFile/before/foo/test.kt b/idea/testData/refactoring/copy/copyClassToNewFile/before/foo/test.kt new file mode 100644 index 00000000000..52166ff2636 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToNewFile/before/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() +} diff --git a/idea/testData/refactoring/copy/copyClassToNewFile/copyClassToNewFile.test b/idea/testData/refactoring/copy/copyClassToNewFile/copyClassToNewFile.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassToNewFile/copyClassToNewFile.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/testData/refactoring/copy/copyClassWithCompanionRefs/after/bar/A.kt b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/after/bar/A.kt new file mode 100644 index 00000000000..aa94d7370b9 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/after/bar/A.kt @@ -0,0 +1,15 @@ +package bar + +class A { + fun test() { + A.testCompanion() + testCompanion() + Companion.testCompanion() + } + + companion object { + fun testCompanion() { + + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassWithCompanionRefs/after/foo/test.kt b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/after/foo/test.kt new file mode 100644 index 00000000000..6acc199879b --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/after/foo/test.kt @@ -0,0 +1,15 @@ +package foo + +class A { + fun test() { + A.testCompanion() + testCompanion() + Companion.testCompanion() + } + + companion object { + fun testCompanion() { + + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassWithCompanionRefs/before/foo/test.kt b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/before/foo/test.kt new file mode 100644 index 00000000000..59fbbc507f3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/before/foo/test.kt @@ -0,0 +1,15 @@ +package foo + +class A { + fun test() { + A.testCompanion() + testCompanion() + Companion.testCompanion() + } + + companion object { + fun testCompanion() { + + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassWithCompanionRefs/copyClassWithCompanionRefs.test b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/copyClassWithCompanionRefs.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithCompanionRefs/copyClassWithCompanionRefs.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/testData/refactoring/copy/copyClassWithRename/after/bar/X.kt b/idea/testData/refactoring/copy/copyClassWithRename/after/bar/X.kt new file mode 100644 index 00000000000..6cece4d4488 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithRename/after/bar/X.kt @@ -0,0 +1,8 @@ +package bar + +import foo.B + +class X { + val a: X = X() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyClassWithRename/after/foo/test.kt b/idea/testData/refactoring/copy/copyClassWithRename/after/foo/test.kt new file mode 100644 index 00000000000..c6688a1d8c2 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithRename/after/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() +} diff --git a/idea/testData/refactoring/copy/copyClassWithRename/before/foo/test.kt b/idea/testData/refactoring/copy/copyClassWithRename/before/foo/test.kt new file mode 100644 index 00000000000..52166ff2636 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithRename/before/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() +} diff --git a/idea/testData/refactoring/copy/copyClassWithRename/copyClassWithRename.test b/idea/testData/refactoring/copy/copyClassWithRename/copyClassWithRename.test new file mode 100644 index 00000000000..e3da26a8489 --- /dev/null +++ b/idea/testData/refactoring/copy/copyClassWithRename/copyClassWithRename.test @@ -0,0 +1,5 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar", + "newName": "X" +} diff --git a/idea/testData/refactoring/copy/copyNestedClass/after/bar/A.kt b/idea/testData/refactoring/copy/copyNestedClass/after/bar/A.kt new file mode 100644 index 00000000000..30d1cb841fe --- /dev/null +++ b/idea/testData/refactoring/copy/copyNestedClass/after/bar/A.kt @@ -0,0 +1,5 @@ +package bar + +class A { + class B +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyNestedClass/after/foo/test.kt b/idea/testData/refactoring/copy/copyNestedClass/after/foo/test.kt new file mode 100644 index 00000000000..75bd8071560 --- /dev/null +++ b/idea/testData/refactoring/copy/copyNestedClass/after/foo/test.kt @@ -0,0 +1,5 @@ +package foo + +class A { + class B +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyNestedClass/before/foo/test.kt b/idea/testData/refactoring/copy/copyNestedClass/before/foo/test.kt new file mode 100644 index 00000000000..36b64450223 --- /dev/null +++ b/idea/testData/refactoring/copy/copyNestedClass/before/foo/test.kt @@ -0,0 +1,5 @@ +package foo + +class A { + class B +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyNestedClass/copyNestedClass.test b/idea/testData/refactoring/copy/copyNestedClass/copyNestedClass.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyNestedClass/copyNestedClass.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/testData/refactoring/copy/copyObject/after/bar/A.kt b/idea/testData/refactoring/copy/copyObject/after/bar/A.kt new file mode 100644 index 00000000000..0c527eb9944 --- /dev/null +++ b/idea/testData/refactoring/copy/copyObject/after/bar/A.kt @@ -0,0 +1,8 @@ +package bar + +import foo.B + +object A { + val a: A = A + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copyObject/after/foo/test.kt b/idea/testData/refactoring/copy/copyObject/after/foo/test.kt new file mode 100644 index 00000000000..7fdb9b2e4c7 --- /dev/null +++ b/idea/testData/refactoring/copy/copyObject/after/foo/test.kt @@ -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() +} diff --git a/idea/testData/refactoring/copy/copyObject/before/foo/test.kt b/idea/testData/refactoring/copy/copyObject/before/foo/test.kt new file mode 100644 index 00000000000..56b120f4194 --- /dev/null +++ b/idea/testData/refactoring/copy/copyObject/before/foo/test.kt @@ -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() +} diff --git a/idea/testData/refactoring/copy/copyObject/copyObject.test b/idea/testData/refactoring/copy/copyObject/copyObject.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copyObject/copyObject.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/testData/refactoring/copy/copySingleClassFile/after/bar/A.kt b/idea/testData/refactoring/copy/copySingleClassFile/after/bar/A.kt new file mode 100644 index 00000000000..d0aced4dda0 --- /dev/null +++ b/idea/testData/refactoring/copy/copySingleClassFile/after/bar/A.kt @@ -0,0 +1,3 @@ +package bar + +class A \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copySingleClassFile/after/foo/test.kt b/idea/testData/refactoring/copy/copySingleClassFile/after/foo/test.kt new file mode 100644 index 00000000000..16d999e4a49 --- /dev/null +++ b/idea/testData/refactoring/copy/copySingleClassFile/after/foo/test.kt @@ -0,0 +1,3 @@ +package foo + +class A \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copySingleClassFile/before/foo/test.kt b/idea/testData/refactoring/copy/copySingleClassFile/before/foo/test.kt new file mode 100644 index 00000000000..16d999e4a49 --- /dev/null +++ b/idea/testData/refactoring/copy/copySingleClassFile/before/foo/test.kt @@ -0,0 +1,3 @@ +package foo + +class A \ No newline at end of file diff --git a/idea/testData/refactoring/copy/copySingleClassFile/copySingleClassFile.test b/idea/testData/refactoring/copy/copySingleClassFile/copySingleClassFile.test new file mode 100644 index 00000000000..f169eabdfa3 --- /dev/null +++ b/idea/testData/refactoring/copy/copySingleClassFile/copySingleClassFile.test @@ -0,0 +1,4 @@ +{ + "mainFile": "foo/test.kt", + "targetPackage": "bar" +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/AbstractMultifileRefactoringTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/AbstractMultifileRefactoringTest.kt new file mode 100644 index 00000000000..cb5b3fc0eb2 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/AbstractMultifileRefactoringTest.kt @@ -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, 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) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt new file mode 100644 index 00000000000..9a67c57ab14 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt @@ -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, 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) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/CopyTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/CopyTestGenerated.java new file mode 100644 index 00000000000..4d3ca5cb27d --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/CopyTestGenerated.java @@ -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); + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt index ee20a47dae7..8f043d91650 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt @@ -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, config: JsonObject) { val members = elementsAtCaret.map { it.getNonStrictParentOfType()!! } @@ -387,6 +291,4 @@ enum class MoveAction { MoveKotlinDeclarationsProcessor(descriptor).run() } }; - - abstract fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List, config: JsonObject) }