Copy: Support class copying

#KT-8180 Fixed
 #KT-9054 Fixed
This commit is contained in:
Alexey Sedunov
2017-05-11 21:28:49 +03:00
parent 405a28648f
commit 92446df14a
43 changed files with 989 additions and 124 deletions
@@ -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)
}