Move: Add conflict checking for "Move packages" case

This commit is contained in:
Alexey Sedunov
2017-04-10 14:30:13 +03:00
parent e21ff23e26
commit cd6177534a
22 changed files with 328 additions and 23 deletions
+4
View File
@@ -367,6 +367,10 @@
id="kotlin.moveTopLevelDeclarations"
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsHandler"
order="first,before kotlin.moveFilesOrDirectories"/>
<refactoring.moveHandler
id="kotlinAwareJavaMoveClassesOrPackagesHandler"
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareJavaMoveClassesOrPackagesHandler"
order="first" />
<moveFileHandler implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinFileHandler" />
<moveFileHandler
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.FqNameFixingMoveJavaFileHandler"
@@ -0,0 +1,97 @@
/*
* 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.move.moveClassesOrPackages
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.MoveDestination
import com.intellij.usageView.UsageInfo
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.refactoring.move.createMoveUsageInfoIfPossible
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.idea.stubindex.KotlinExactPackagesIndex
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
class KotlinAwareDelegatingMoveDestination(
private val delegate: MoveDestination,
private val targetPackage: PsiPackage?,
private val targetDirectory: PsiDirectory?
) : MoveDestination by delegate {
override fun analyzeModuleConflicts(
elements: MutableCollection<PsiElement>,
conflicts: MultiMap<PsiElement, String>,
usages: Array<out UsageInfo>
) {
delegate.analyzeModuleConflicts(elements, conflicts, usages)
if (targetPackage == null || targetDirectory == null) return
val project = targetDirectory.project
val moveTarget = KotlinDirectoryMoveTarget(FqName(targetPackage.qualifiedName), targetDirectory)
val packagesIndex = KotlinExactPackagesIndex.getInstance()
val directoriesToMove = elements.flatMapTo(LinkedHashSet<PsiDirectory>()) {
(it as? PsiPackage)?.directories?.toList() ?: emptyList()
}
val projectScope = project.projectScope()
val filesToProcess = elements.flatMapTo(LinkedHashSet<KtFile>()) {
if (it is PsiPackage) packagesIndex[it.qualifiedName, project, projectScope] else emptyList()
}
val extraElementsForReferenceSearch = LinkedHashSet<PsiElement>()
val extraElementCollector = object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is KtNamedDeclaration && element.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
element.parentsWithSelf.lastOrNull { it is KtNamedDeclaration }?.let { extraElementsForReferenceSearch += it }
stopWalking()
}
super.visitElement(element)
}
}
filesToProcess.flatMap {it.declarations}.forEach { it.accept(extraElementCollector) }
val progressIndicator = ProgressManager.getInstance().progressIndicator
progressIndicator?.pushState()
val extraUsages = ArrayList<UsageInfo>()
try {
progressIndicator.text = "Looking for Usages"
for ((index, element) in extraElementsForReferenceSearch.withIndex()) {
progressIndicator.fraction = (index + 1)/extraElementsForReferenceSearch.size.toDouble()
ReferencesSearch.search(element, projectScope).mapNotNullTo(extraUsages) { ref ->
createMoveUsageInfoIfPossible(ref, element, true, false)
}
}
}
finally {
progressIndicator?.popState()
}
filesToProcess.forEach {
analyzeConflictsInFile(it, extraUsages, moveTarget, directoriesToMove, conflicts) {}
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.move.moveClassesOrPackages
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.move.moveClassesOrPackages.JavaMoveClassesOrPackagesHandler
class KotlinAwareJavaMoveClassesOrPackagesHandler : JavaMoveClassesOrPackagesHandler() {
override fun createMoveClassesOrPackagesToNewDirectoryDialog(
directory: PsiDirectory,
elementsToMove: Array<out PsiElement>,
moveCallback: MoveCallback?
) = KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog(directory, elementsToMove, moveCallback)
}
@@ -0,0 +1,35 @@
/*
* 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.move.moveClassesOrPackages
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiPackage
import com.intellij.refactoring.MoveDestination
import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesToNewDirectoryDialog
class KotlinAwareMoveClassesOrPackagesToNewDirectoryDialog(
directory: PsiDirectory,
elementsToMove: Array<out PsiElement>,
moveCallback: MoveCallback?
) : MoveClassesOrPackagesToNewDirectoryDialog(directory, elementsToMove, moveCallback) {
override fun createDestination(aPackage: PsiPackage, directory: PsiDirectory): MoveDestination {
val delegate = super.createDestination(aPackage, directory)
return KotlinAwareDelegatingMoveDestination(delegate, aPackage, directory)
}
}
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.refactoring.getUsageContext
import org.jetbrains.kotlin.idea.refactoring.move.KotlinMoveUsage
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.contains
@@ -396,3 +397,32 @@ class MoveConflictChecker(
checkInternalMemberUsages(conflicts)
}
}
fun analyzeConflictsInFile(
file: KtFile,
usages: Collection<UsageInfo>,
moveTarget: KotlinMoveTarget,
allElementsToMove: Collection<PsiElement>,
conflicts: MultiMap<PsiElement, String>,
onUsageUpdate: (List<UsageInfo>) -> Unit
) {
val elementsToMove = file.declarations
if (elementsToMove.isEmpty()) return
val (internalUsages, externalUsages) = usages.partition { it is KotlinMoveUsage && it.isInternal }
val internalUsageSet = internalUsages.toMutableSet()
val externalUsageSet = externalUsages.toMutableSet()
val conflictChecker = MoveConflictChecker(
file.project,
elementsToMove,
moveTarget,
elementsToMove.first(),
allElementsToMove = allElementsToMove
)
conflictChecker.checkAllConflicts(externalUsageSet, internalUsageSet, conflicts)
if (externalUsageSet.size != externalUsages.size || internalUsageSet.size != internalUsages.size) {
onUsageUpdate((externalUsageSet + internalUsageSet).toList())
}
}
@@ -29,10 +29,9 @@ import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.refactoring.invokeOnceOnCommandFinish
import org.jetbrains.kotlin.idea.refactoring.move.KotlinMoveUsage
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveConflictChecker
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import java.util.*
@@ -84,23 +83,8 @@ class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
for ((index, usageInfo) in infos.withIndex()) {
if (usageInfo !is FileUsagesWrapper) continue
val elementsToMove = usageInfo.psiFile.declarations
if (elementsToMove.isEmpty()) continue
val (internalUsages, externalUsages) = usageInfo.usages.partition { it is KotlinMoveUsage && it.isInternal }
val internalUsageSet = internalUsages.toMutableSet()
val externalUsageSet = externalUsages.toMutableSet()
val conflictChecker = MoveConflictChecker(
project,
elementsToMove,
moveTarget,
elementsToMove.first(),
allElementsToMove = files
)
conflictChecker.checkAllConflicts(externalUsageSet, internalUsageSet, conflicts)
if (externalUsageSet.size != externalUsages.size || internalUsageSet.size != internalUsages.size) {
infos[index] = usageInfo.copy(usages = (externalUsageSet + internalUsageSet).toList())
analyzeConflictsInFile(usageInfo.psiFile, usageInfo.usages, moveTarget, files, conflicts) {
infos[index] = usageInfo.copy(usages = it)
}
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,8 @@
package test.pack
import test2.J
class Bar {
internal val foo = Foo()
val j = J()
}
@@ -0,0 +1,7 @@
package test.pack
import test2.J
internal class Foo {
val j = J()
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,7 @@
package test2;
import test.pack.Foo;
public class J {
Foo foo = new Foo();
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,7 @@
package test2;
import test2.pack.Foo;
public class J {
Foo foo = new Foo();
}
@@ -0,0 +1,8 @@
package test2.pack
import test2.J
class Bar {
internal val foo = Foo()
val j = J()
}
@@ -0,0 +1,7 @@
package test2.pack
import test2.J
internal class Foo {
val j = J()
}
@@ -0,0 +1,6 @@
Class test2.J, referenced in property test2.pack.Bar.j, will not be accessible in module A
Class test2.J, referenced in property test2.pack.Foo.j, will not be accessible in module A
Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B
Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B
Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B
Class test2.pack.Foo, referenced in field J.foo, will not be accessible from module B
@@ -0,0 +1,6 @@
{
"mainFile": "B/src/test2/pack/Bar.kt",
"type": "MOVE_PACKAGES",
"sourcePackage": "test2.pack",
"targetPackage": "test"
}
@@ -22,6 +22,7 @@ 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
@@ -44,6 +45,7 @@ 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.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
@@ -185,13 +187,30 @@ enum class MoveAction {
MOVE_PACKAGES {
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
val project = mainFile.project
val sourcePackage = config.getString("sourcePackage")
val targetPackage = config.getString("targetPackage")
val sourcePackageName = config.getString("sourcePackage")
val targetPackageName = config.getString("targetPackage")
val sourcePackage = JavaPsiFacade.getInstance(project).findPackage(sourcePackageName)!!
val targetPackage = JavaPsiFacade.getInstance(project).findPackage(targetPackageName)
val targetDirectory = targetPackage?.directories?.first()
val targetPackageWrapper = PackageWrapper(mainFile.manager, targetPackageName)
val moveDestination = if (targetDirectory != null) {
val targetSourceRoot = ProjectRootManager.getInstance(project).fileIndex.getSourceRootForFile(targetDirectory.virtualFile)!!
KotlinAwareDelegatingMoveDestination(
AutocreatingSingleSourceRootMoveDestination(targetPackageWrapper, targetSourceRoot),
targetPackage,
targetDirectory
)
}
else {
MultipleRootsMoveDestination(targetPackageWrapper)
}
MoveClassesOrPackagesProcessor(
project,
arrayOf(JavaPsiFacade.getInstance(project).findPackage(sourcePackage)!!),
MultipleRootsMoveDestination(PackageWrapper(mainFile.manager, targetPackage)),
arrayOf(sourcePackage),
moveDestination,
/* searchInComments = */ false,
/* searchInNonJavaFiles = */ true,
/* moveCallback = */ null
@@ -78,6 +78,12 @@ public class MultiModuleMoveTestGenerated extends AbstractMultiModuleMoveTest {
doTest(fileName);
}
@TestMetadata("movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test")
public void testMovePackageToUnrelatedModuleConflict_MovePackageToUnrelatedModuleConflict() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/moveMultiModule/movePackageToUnrelatedModuleConflict/movePackageToUnrelatedModuleConflict.test");
doTest(fileName);
}
@TestMetadata("moveToModuleWithoutLibConflict/moveToModuleWithoutLibConflict.test")
public void testMoveToModuleWithoutLibConflict_MoveToModuleWithoutLibConflict() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/moveMultiModule/moveToModuleWithoutLibConflict/moveToModuleWithoutLibConflict.test");