Move: Add conflict checking for "Move packages" case
This commit is contained in:
@@ -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"
|
||||
|
||||
+97
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -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)
|
||||
}
|
||||
+35
@@ -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)
|
||||
}
|
||||
}
|
||||
+30
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
+3
-19
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+11
@@ -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>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package test.pack
|
||||
|
||||
import test2.J
|
||||
|
||||
class Bar {
|
||||
internal val foo = Foo()
|
||||
val j = J()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test.pack
|
||||
|
||||
import test2.J
|
||||
|
||||
internal class Foo {
|
||||
val j = J()
|
||||
}
|
||||
Vendored
+11
@@ -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>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test2;
|
||||
|
||||
import test.pack.Foo;
|
||||
|
||||
public class J {
|
||||
Foo foo = new Foo();
|
||||
}
|
||||
Vendored
+11
@@ -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>
|
||||
Vendored
+11
@@ -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>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test2;
|
||||
|
||||
import test2.pack.Foo;
|
||||
|
||||
public class J {
|
||||
Foo foo = new Foo();
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package test2.pack
|
||||
|
||||
import test2.J
|
||||
|
||||
class Bar {
|
||||
internal val foo = Foo()
|
||||
val j = J()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test2.pack
|
||||
|
||||
import test2.J
|
||||
|
||||
internal class Foo {
|
||||
val j = J()
|
||||
}
|
||||
Vendored
+6
@@ -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
|
||||
+6
@@ -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
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user