Move: Implement conflict checking for internal members
#KT-13190 Fixed
This commit is contained in:
+37
-8
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiElement
|
|||||||
import com.intellij.psi.PsiMember
|
import com.intellij.psi.PsiMember
|
||||||
import com.intellij.psi.PsiMethod
|
import com.intellij.psi.PsiMethod
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
import com.intellij.refactoring.util.*
|
import com.intellij.refactoring.util.*
|
||||||
import com.intellij.usageView.UsageInfo
|
import com.intellij.usageView.UsageInfo
|
||||||
@@ -35,11 +36,9 @@ import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor
|
|||||||
import org.jetbrains.kotlin.idea.caches.resolve.*
|
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getUsageContext
|
import org.jetbrains.kotlin.idea.refactoring.getUsageContext
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.contains
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
@@ -83,13 +82,13 @@ class MoveConflictChecker(
|
|||||||
|
|
||||||
is KotlinDirectoryBasedMoveTarget -> {
|
is KotlinDirectoryBasedMoveTarget -> {
|
||||||
val packageFqName = targetContainerFqName ?: return null
|
val packageFqName = targetContainerFqName ?: return null
|
||||||
val targetDir = directory
|
val targetDir = directory?.virtualFile ?: targetFile
|
||||||
val targetModuleDescriptor = if (targetDir != null) {
|
val targetModuleDescriptor = if (targetDir != null) {
|
||||||
val targetModule = ModuleUtilCore.findModuleForPsiElement(targetDir) ?: return null
|
val targetModule = ModuleUtilCore.findModuleForFile(targetDir, project) ?: return null
|
||||||
val moduleFileIndex = ModuleRootManager.getInstance(targetModule).fileIndex
|
val moduleFileIndex = ModuleRootManager.getInstance(targetModule).fileIndex
|
||||||
val targetModuleInfo = when {
|
val targetModuleInfo = when {
|
||||||
moduleFileIndex.isInSourceContent(targetDir.virtualFile) -> targetModule.productionSourceInfo()
|
moduleFileIndex.isInSourceContent(targetDir) -> targetModule.productionSourceInfo()
|
||||||
moduleFileIndex.isInTestSourceContent(targetDir.virtualFile) -> targetModule.testSourceInfo()
|
moduleFileIndex.isInTestSourceContent(targetDir) -> targetModule.testSourceInfo()
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
resolutionFacade.findModuleDescriptor(targetModuleInfo) ?: return null
|
resolutionFacade.findModuleDescriptor(targetModuleInfo) ?: return null
|
||||||
@@ -242,6 +241,35 @@ class MoveConflictChecker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isToBeMoved(element: PsiElement): Boolean = elementsToMove.any { it.isAncestor(element, false) }
|
||||||
|
|
||||||
|
private fun checkInternalMemberUsages(conflicts: MultiMap<PsiElement, String>) {
|
||||||
|
val sourceRoot = moveTarget.targetFile ?: return
|
||||||
|
val targetModule = ModuleUtilCore.findModuleForFile(sourceRoot, project) ?: return
|
||||||
|
|
||||||
|
val membersToCheck = LinkedHashSet<KtDeclaration>()
|
||||||
|
val memberCollector = object : KtVisitorVoid() {
|
||||||
|
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
|
||||||
|
val declarations = classOrObject.declarations
|
||||||
|
declarations.filterTo(membersToCheck) { it.hasModifier(KtTokens.INTERNAL_KEYWORD) }
|
||||||
|
declarations.forEach { it.accept(this) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elementsToMove.forEach { it.accept(memberCollector) }
|
||||||
|
|
||||||
|
for (memberToCheck in membersToCheck) {
|
||||||
|
for (reference in ReferencesSearch.search(memberToCheck)) {
|
||||||
|
val element = reference.element ?: continue
|
||||||
|
val usageModule = ModuleUtilCore.findModuleForPsiElement(element)
|
||||||
|
if (usageModule != targetModule && !isToBeMoved(element)) {
|
||||||
|
val container = element.getUsageContext()
|
||||||
|
val message = "${render(container)} uses internal ${render(memberToCheck)} which will be inaccessible after move"
|
||||||
|
conflicts.putValue(element, message.capitalize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun checkAllConflicts(
|
fun checkAllConflicts(
|
||||||
externalUsages: MutableSet<UsageInfo>,
|
externalUsages: MutableSet<UsageInfo>,
|
||||||
internalUsages: MutableSet<UsageInfo>,
|
internalUsages: MutableSet<UsageInfo>,
|
||||||
@@ -251,5 +279,6 @@ class MoveConflictChecker(
|
|||||||
checkModuleConflictsInDeclarations(internalUsages, conflicts)
|
checkModuleConflictsInDeclarations(internalUsages, conflicts)
|
||||||
checkVisibilityInUsages(externalUsages, conflicts)
|
checkVisibilityInUsages(externalUsages, conflicts)
|
||||||
checkVisibilityInDeclarations(conflicts)
|
checkVisibilityInDeclarations(conflicts)
|
||||||
|
checkInternalMemberUsages(conflicts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<?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" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
<orderEntry type="module" module-name="B" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package packA1
|
||||||
|
|
||||||
|
import packA2.InternalContentUser
|
||||||
|
|
||||||
|
class More
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package packA2
|
||||||
|
|
||||||
|
import packA1.InternalContent
|
||||||
|
|
||||||
|
class InternalContentUser {
|
||||||
|
fun useInternal(p: InternalContent) = p.internalFun()
|
||||||
|
|
||||||
|
internal fun internalFun() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?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" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package packB
|
||||||
|
|
||||||
|
class InternalContent {
|
||||||
|
internal fun internalFun() {}
|
||||||
|
|
||||||
|
fun useInternalInside() {
|
||||||
|
internalFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun useInternal() {
|
||||||
|
InternalContentUser().internalFun()
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
This file is needed to ensure that containing directory is under version control
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<?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" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
<orderEntry type="module" module-name="B" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package packA1
|
||||||
|
|
||||||
|
import packA2.InternalContentUser
|
||||||
|
|
||||||
|
class <caret>InternalContent {
|
||||||
|
internal fun internalFun() {}
|
||||||
|
|
||||||
|
fun useInternalInside() {
|
||||||
|
internalFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun useInternal() {
|
||||||
|
InternalContentUser().internalFun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class More
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package packA2
|
||||||
|
|
||||||
|
import packA1.InternalContent
|
||||||
|
|
||||||
|
class InternalContentUser {
|
||||||
|
fun useInternal(p: InternalContent) = p.internalFun()
|
||||||
|
|
||||||
|
internal fun internalFun() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?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" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
This file is needed to ensure that containing directory is under version control
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
Class InternalContent uses function internalFun() which will be inaccessible after move
|
||||||
|
Class packA1.InternalContent, referenced in file InternalSource.kt, will not be accessible from module A
|
||||||
|
Class packA1.InternalContent, referenced in file InternalSource.kt, will not be accessible from module A
|
||||||
|
Class packA2.InternalContentUser, referenced in function packA1.InternalContent.useInternal(), will not be accessible in module B
|
||||||
|
Function packA2.InternalContentUser.internalFun(), referenced in function packA1.InternalContent.useInternal(), will not be accessible in module B
|
||||||
|
Function useInternal(InternalContent) uses internal function internalFun() which will be inaccessible after move
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "A/src/packA1/InternalTarget.kt",
|
||||||
|
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
|
||||||
|
"targetPackage": "packB",
|
||||||
|
"targetSourceRoot": "B/src",
|
||||||
|
"isMultiModule": "true",
|
||||||
|
"withRuntime": "true"
|
||||||
|
}
|
||||||
@@ -492,6 +492,12 @@ public class MoveTestGenerated extends AbstractMoveTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/moveInternalToAnotherModule/moveInternalToAnotherModule.test")
|
||||||
|
public void testKotlin_moveTopLevelDeclarations_misc_moveInternalToAnotherModule_MoveInternalToAnotherModule() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveInternalToAnotherModule/moveInternalToAnotherModule.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/moveToUnrelatedModuleConflict.test")
|
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/moveToUnrelatedModuleConflict.test")
|
||||||
public void testKotlin_moveTopLevelDeclarations_misc_moveToUnrelatedModuleConflict_MoveToUnrelatedModuleConflict() throws Exception {
|
public void testKotlin_moveTopLevelDeclarations_misc_moveToUnrelatedModuleConflict_MoveToUnrelatedModuleConflict() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/moveToUnrelatedModuleConflict.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/moveToUnrelatedModuleConflict.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user