mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] Rework internal usage processing and remove isUpdatable
Removes isUpdatable filters after conflict checking and instead of filtering, the refactoring now marks usages whether they are updatable during the marking of internal usages. Because of recent changes that made it so we only call find usages on top level moved elements, this means external usages are also always updatable. #KTIJ-30733 Fixed GitOrigin-RevId: 5685845ae45c4639f81036ab8e4c72a1946e26f0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b5e4110847
commit
9a1d5ea169
+5
@@ -144,6 +144,11 @@ public abstract class MoveTestGenerated extends AbstractMoveTest {
|
||||
runTest("testData/refactoring/moveFile/java/moveFileToAnotherPackage/moveFileToAnotherPackage.test");
|
||||
}
|
||||
|
||||
@TestMetadata("java/movePackageWithDestructuringReference/movePackageWithDestructuringReference.test")
|
||||
public void testJava_movePackageWithDestructuringReference_MovePackageWithDestructuringReference() throws Exception {
|
||||
runTest("testData/refactoring/moveFile/java/movePackageWithDestructuringReference/movePackageWithDestructuringReference.test");
|
||||
}
|
||||
|
||||
@TestMetadata("kotlin/addExtensionImport/addExtensionImport.test")
|
||||
public void testKotlin_addExtensionImport_AddExtensionImport() throws Exception {
|
||||
runTest("testData/refactoring/moveFile/kotlin/addExtensionImport/addExtensionImport.test");
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class RandomJavaFile {
|
||||
// Just here to invoke Java refactoring
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package zero.destructuring
|
||||
|
||||
class MovingComponent {
|
||||
operator fun component1() = 1
|
||||
operator fun component2() = 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package zero
|
||||
|
||||
import zero.destructuring.MovingComponent
|
||||
|
||||
fun referComponent() {
|
||||
val (x, y) = MovingComponent()
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class RandomJavaFile {
|
||||
// Just here to invoke Java refactoring
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package one.destructuring
|
||||
|
||||
class MovingComponent {
|
||||
operator fun component1() = 1
|
||||
operator fun component2() = 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package zero
|
||||
|
||||
import one.destructuring.MovingComponent
|
||||
|
||||
fun referComponent() {
|
||||
val (x, y) = MovingComponent()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mainFile": "RandomJavaFile.java",
|
||||
"filesToMove": ["one/destructuring"],
|
||||
"type": "MOVE_FILES",
|
||||
"targetPackage": "zero",
|
||||
"enabledInK1": "false",
|
||||
"enabledInK2": "true"
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package b
|
||||
|
||||
import b.Factory.Companion.invoke
|
||||
import java.util.function.IntPredicate
|
||||
|
||||
interface Factory {
|
||||
operator fun invoke(i: Int): IntPredicate
|
||||
|
||||
companion object {
|
||||
inline operator fun invoke(crossinline f: (Int) -> IntPredicate) = object : Factory {
|
||||
override fun invoke(i: Int) = f(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun foo(): Factory = Factory { k ->
|
||||
IntPredicate { n -> n % k == 0 }
|
||||
}
|
||||
+3
-4
@@ -35,7 +35,6 @@ import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsage
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.retargetInternalUsages
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.retargetInternalUsagesForCopyFile
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.unMarkAllUsages
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.unMarkNonUpdatableUsages
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.conflict.checkModuleDependencyConflictsForInternalUsages
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.conflict.checkVisibilityConflictsForInternalUsages
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.createCopyTarget
|
||||
@@ -165,7 +164,9 @@ class CopyKotlinDeclarationsHandler : AbstractCopyKotlinDeclarationsHandler() {
|
||||
val targetData = getTargetData(sourceData) ?: return
|
||||
|
||||
for (element in elementsToCopy) {
|
||||
markInternalUsages(element)
|
||||
analyzeInModalWindow(elementsToCopy.first(), RefactoringBundle.message("refactoring.preprocess.usages.progress")) {
|
||||
markInternalUsages(element, element)
|
||||
}
|
||||
}
|
||||
|
||||
val conflicts: MultiMap<PsiElement, String> =
|
||||
@@ -173,8 +174,6 @@ class CopyKotlinDeclarationsHandler : AbstractCopyKotlinDeclarationsHandler() {
|
||||
collectConflicts(sourceData, targetData)
|
||||
}
|
||||
|
||||
unMarkNonUpdatableUsages(elementsToCopy)
|
||||
|
||||
project.checkConflictsInteractively(conflicts) {
|
||||
try {
|
||||
ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(copyCommandName, project, null) {
|
||||
|
||||
+1
-6
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
|
||||
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2ChangePackageDescriptor
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.unMarkNonUpdatableUsages
|
||||
|
||||
class K2ChangePackageRefactoringProcessor(private val descriptor: K2ChangePackageDescriptor) : BaseRefactoringProcessor(descriptor.project) {
|
||||
override fun getCommandName(): String = KotlinBundle.message(
|
||||
@@ -42,11 +41,7 @@ class K2ChangePackageRefactoringProcessor(private val descriptor: K2ChangePackag
|
||||
usages.filterIsInstance<MoveRenameUsageInfo>()
|
||||
)
|
||||
}
|
||||
val toContinue = showConflicts(conflicts, usages)
|
||||
if (!toContinue) return false
|
||||
unMarkNonUpdatableUsages(descriptor.files)
|
||||
refUsages.set(K2MoveRenameUsageInfo.filterUpdatable(descriptor.files, usages).toTypedArray())
|
||||
return true
|
||||
return showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
@OptIn(KaAllowAnalysisOnEdt::class)
|
||||
|
||||
+1
-6
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
|
||||
import org.jetbrains.kotlin.idea.base.psi.deleteSingle
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveOperationDescriptor
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.unMarkNonUpdatableUsages
|
||||
|
||||
class K2MoveDeclarationsRefactoringProcessor(
|
||||
private val operationDescriptor: K2MoveOperationDescriptor.Declarations
|
||||
@@ -57,11 +56,7 @@ class K2MoveDeclarationsRefactoringProcessor(
|
||||
}
|
||||
}
|
||||
}
|
||||
val toContinue = showConflicts(conflicts, usages)
|
||||
if (!toContinue) return false
|
||||
unMarkNonUpdatableUsages(operationDescriptor.sourceElements)
|
||||
refUsages.set(K2MoveRenameUsageInfo.filterUpdatable(operationDescriptor.sourceElements, usages).toTypedArray())
|
||||
return true
|
||||
return showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
@OptIn(KaAllowAnalysisOnEdt::class)
|
||||
|
||||
+1
-5
@@ -14,7 +14,6 @@ import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectori
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.Function
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.unMarkNonUpdatableUsages
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
@@ -65,12 +64,9 @@ class K2MoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
|
||||
targetDirectory: PsiDirectory?,
|
||||
conflicts: MultiMap<PsiElement, String>
|
||||
) {
|
||||
val movedFiles = files.filterIsInstance<KtFile>()
|
||||
unMarkNonUpdatableUsages(movedFiles)
|
||||
|
||||
// processing kotlin usages from Java declarations will result in non-deterministic retargeting of the references
|
||||
// to fix this, all usages are sorted by start offset
|
||||
infos.set(K2MoveRenameUsageInfo.filterUpdatable(movedFiles, infos.get()).sortedBy { it.element?.startOffset ?: -1 }.toTypedArray())
|
||||
infos.get().sortedBy { it.element?.startOffset ?: -1 }
|
||||
if (targetDirectory != null) { // TODO probably this should never be null but it happens when there are multiple source roots
|
||||
moveFileHandler.detectConflicts(conflicts, files.filterIsInstance<KtFile>().toTypedArray(), infos.get(), targetDirectory)
|
||||
}
|
||||
|
||||
+1
-23
@@ -3,7 +3,6 @@ package org.jetbrains.kotlin.idea.k2.refactoring.move.processor
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -18,7 +17,6 @@ import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
|
||||
import org.jetbrains.kotlin.idea.core.getFqNameWithImplicitPrefix
|
||||
import org.jetbrains.kotlin.idea.core.getFqNameWithImplicitPrefixOrRoot
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveOperationDescriptor
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.unMarkNonUpdatableUsages
|
||||
import org.jetbrains.kotlin.psi.CopyablePsiUserDataProperty
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
@@ -31,27 +29,7 @@ class K2MoveFilesOrDirectoriesRefactoringProcessor(descriptor: K2MoveOperationDe
|
||||
descriptor.searchForText,
|
||||
descriptor.moveCallBack,
|
||||
Runnable { }
|
||||
) {
|
||||
private fun PsiElement.allFiles(): List<KtFile> = when (this) {
|
||||
is PsiDirectory -> children.flatMap { it.allFiles() }
|
||||
is KtFile -> listOf(this)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
override fun preprocessUsages(refUsages: Ref<Array<out UsageInfo>>): Boolean {
|
||||
val toContinue = super.preprocessUsages(refUsages)
|
||||
if (!toContinue) return false
|
||||
|
||||
// after conflict checking, we don't need non-updatable usages anymore
|
||||
val elementsToMove = myElementsToMove.toList()
|
||||
unMarkNonUpdatableUsages(elementsToMove)
|
||||
val updatableUsages = K2MoveRenameUsageInfo.filterUpdatable(elementsToMove, refUsages.get())
|
||||
refUsages.set(updatableUsages.toTypedArray())
|
||||
val usagesByFile = updatableUsages.groupBy { (it as? K2MoveRenameUsageInfo)?.referencedElement?.containingFile }
|
||||
usagesByFile.forEach { file, usages -> myFoundUsages.replace(file, usages) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
class K2MoveFilesHandler : MoveFileHandler() {
|
||||
/**
|
||||
|
||||
+104
-129
@@ -13,17 +13,15 @@ import com.intellij.refactoring.move.moveMembers.MoveMembersOptions
|
||||
import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor
|
||||
import com.intellij.refactoring.util.MoveRenameUsageInfo
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import org.jetbrains.kotlin.analysis.api.KaSession
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
|
||||
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaDeclarationContainerSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.types.KaFunctionType
|
||||
import org.jetbrains.kotlin.asJava.toLightElements
|
||||
import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences
|
||||
import org.jetbrains.kotlin.idea.base.psi.canBeUsedInImport
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.internalUsageInfo
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.updatableUsageInfo
|
||||
import org.jetbrains.kotlin.idea.references.KtConstructorDelegationReference
|
||||
import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
@@ -41,14 +39,6 @@ sealed class K2MoveRenameUsageInfo(
|
||||
reference: PsiReference,
|
||||
referencedElement: PsiNamedElement
|
||||
) : MoveRenameUsageInfo(element, reference, referencedElement) {
|
||||
/**
|
||||
* Returns whether this usage info is actually required for updating.
|
||||
* Sometimes it can depend on the language of the usage whether the usage info is updatable.
|
||||
* In Kotlin, for example, object members can be imported and might require updating, but in Java these are regular instance methods and
|
||||
* references to these methods can't be updated.
|
||||
*/
|
||||
abstract fun isUpdatable(movedElements: List<KtNamedDeclaration>): Boolean
|
||||
|
||||
abstract fun retarget(to: PsiNamedElement): PsiElement?
|
||||
|
||||
/**
|
||||
@@ -62,10 +52,6 @@ sealed class K2MoveRenameUsageInfo(
|
||||
private val oldContainingFqn: String?,
|
||||
private val lightElementIndex: Int,
|
||||
) : K2MoveRenameUsageInfo(element, reference, referencedElement) {
|
||||
override fun isUpdatable(movedElements: List<KtNamedDeclaration>): Boolean {
|
||||
return true // TODO write better updatable check for light references
|
||||
}
|
||||
|
||||
override fun retarget(to: PsiNamedElement): PsiElement? {
|
||||
if (to !is KtNamedDeclaration) error("Usage must reference a Kotlin element")
|
||||
val element = element ?: return element
|
||||
@@ -129,71 +115,6 @@ sealed class K2MoveRenameUsageInfo(
|
||||
referencedElement: PsiNamedElement,
|
||||
val isInternal: Boolean
|
||||
) : K2MoveRenameUsageInfo(element, reference, referencedElement) {
|
||||
@OptIn(KaAllowAnalysisOnEdt::class)
|
||||
override fun isUpdatable(movedElements: List<KtNamedDeclaration>): Boolean = allowAnalysisOnEdt {
|
||||
val refExpr = element as? KtSimpleNameExpression ?: return false
|
||||
if (!refExpr.canBeUsedInImport()) return false
|
||||
if (refExpr.parentOfType<KtImportDirective>(withSelf = false) != null) return true
|
||||
if (refExpr.isUnqualifiable()) return true
|
||||
val refChain = (refExpr.getTopmostParentQualifiedExpressionForReceiver() ?: refExpr)
|
||||
.collectDescendantsOfType<KtSimpleNameExpression>()
|
||||
.filter { it.canBeUsedInImport() && it.isNameDeterminantInQualifiedChain() }
|
||||
return if (isInternal) {
|
||||
// for internal usages, update the first name determinant in the call chain
|
||||
refChain.firstOrNull() == refExpr
|
||||
} else {
|
||||
// for external usages, update the first reference to a moved element
|
||||
refChain.firstOrNull { simpleNameExpr -> simpleNameExpr.mainReference.resolve() in movedElements } == refExpr
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtSimpleNameExpression.getTopmostParentQualifiedExpressionForReceiver(): KtExpression? {
|
||||
return generateSequence<KtExpression>(this) {
|
||||
it.parent as? KtQualifiedExpression ?: it.parent as? KtCallExpression
|
||||
}.lastOrNull()
|
||||
}
|
||||
|
||||
@OptIn(KaAllowAnalysisOnEdt::class)
|
||||
private fun KtSimpleNameExpression.isNameDeterminantInQualifiedChain(): Boolean = allowAnalysisOnEdt {
|
||||
analyze(this) {
|
||||
val resolvedSymbol = mainReference.resolveToSymbol()
|
||||
if (resolvedSymbol is KaClassSymbol && resolvedSymbol.classKind == KaClassKind.COMPANION_OBJECT) return true
|
||||
if (resolvedSymbol is KaConstructorSymbol) return true
|
||||
val containingSymbol = resolvedSymbol?.containingDeclaration
|
||||
if (resolvedSymbol is KaPackageSymbol) return false // ignore packages
|
||||
if (containingSymbol == null) return true // top levels are static
|
||||
if (containingSymbol is KaClassSymbol) {
|
||||
val classKind = containingSymbol.classKind
|
||||
if (classKind == KaClassKind.OBJECT || classKind == KaClassKind.COMPANION_OBJECT) return true
|
||||
}
|
||||
if (containingSymbol is KaDeclarationContainerSymbol) {
|
||||
if (resolvedSymbol in containingSymbol.staticMemberScope.declarations) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtSimpleNameExpression.isUnqualifiable(): Boolean {
|
||||
// example: a.foo() where foo is an extension function
|
||||
fun KtSimpleNameExpression.isExtensionReference(): Boolean {
|
||||
return analyze(this) {
|
||||
val callable = mainReference.resolveToSymbol() as? KaCallableSymbol
|
||||
if (callable?.isExtension == true) return true
|
||||
if (callable is KaPropertySymbol) {
|
||||
val returnType = callable.returnType
|
||||
returnType is KaFunctionType && returnType.receiverType != null
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
// example: ::foo
|
||||
fun KtSimpleNameExpression.isCallableReferenceExpressionWithoutQualifier(): Boolean {
|
||||
val parent = parent
|
||||
return parent is KtCallableReferenceExpression && parent.receiverExpression == null
|
||||
}
|
||||
return isExtensionReference() || isCallableReferenceExpressionWithoutQualifier()
|
||||
}
|
||||
|
||||
fun refresh(element: KtElement, referencedElement: PsiNamedElement): K2MoveRenameUsageInfo {
|
||||
val reference = element.mainReference ?: return this
|
||||
return Source(element, reference, referencedElement, isInternal)
|
||||
@@ -212,7 +133,7 @@ sealed class K2MoveRenameUsageInfo(
|
||||
|
||||
companion object {
|
||||
fun find(declaration: KtNamedDeclaration): List<UsageInfo> {
|
||||
markInternalUsages(declaration)
|
||||
markInternalUsages(declaration, declaration)
|
||||
return preProcessUsages(findExternalUsages(declaration))
|
||||
}
|
||||
|
||||
@@ -229,58 +150,111 @@ sealed class K2MoveRenameUsageInfo(
|
||||
* @see restoreInternalUsages
|
||||
* @see K2MoveRenameUsageInfo.Source.refresh
|
||||
*/
|
||||
internal var KtElement.internalUsageInfo: K2MoveRenameUsageInfo? by CopyablePsiUserDataProperty(Key.create("INTERNAL_USAGE_INFO"))
|
||||
internal val KtElement.internalUsageInfo get() = updatableUsageInfo ?: nonUpdatableUsageInfo
|
||||
|
||||
/**
|
||||
* Finds any usage inside [containing]. We need these usages because when moving [containing] to a different package references
|
||||
* that where previously imported by default might now require an explicit import.
|
||||
* Internal usage info that can be retargeted.
|
||||
*/
|
||||
fun markInternalUsages(containing: KtElement) {
|
||||
containing.forEachDescendantOfType<KDocName> { name ->
|
||||
val reference = name.mainReference
|
||||
val resolved = reference.resolve() as? PsiNamedElement ?: return@forEachDescendantOfType
|
||||
name.internalUsageInfo = Source(name, reference, resolved, true)
|
||||
internal var KtElement.updatableUsageInfo: K2MoveRenameUsageInfo? by CopyablePsiUserDataProperty(Key.create("UPDATABLE_INTERNAL_USAGE_INFO"))
|
||||
|
||||
/**
|
||||
* Internal usage info that can't be retargeted but might still be useful in, for example, conflict checking.
|
||||
*/
|
||||
internal var KtElement.nonUpdatableUsageInfo: K2MoveRenameUsageInfo? by CopyablePsiUserDataProperty(Key.create("NON_UPDATABLE_INTERNAL_USAGE_INFO"))
|
||||
|
||||
/**
|
||||
* Finds any usage inside [elem].
|
||||
* We need these usages because when moving [elem] to a different package references that where previously imported by default might
|
||||
* now require an explicit import.
|
||||
* @see com.intellij.codeInsight.ChangeContextUtil.encodeContextInfo for Java implementation
|
||||
*/
|
||||
fun markInternalUsages(elem: PsiElement, topLevelMoved: KtElement) {
|
||||
when (elem) {
|
||||
is KDocName -> {
|
||||
val reference = elem.mainReference
|
||||
val resolved = reference.resolve() as? PsiNamedElement
|
||||
if (resolved != null && !PsiTreeUtil.isAncestor(topLevelMoved, resolved, false)) {
|
||||
elem.updatableUsageInfo = Source(elem, reference, resolved, true)
|
||||
}
|
||||
}
|
||||
is KtReferenceExpression -> {
|
||||
elem.markInternalUsageInfo(topLevelMoved)
|
||||
}
|
||||
}
|
||||
containing.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
|
||||
if (refExpr is KtEnumEntrySuperclassReferenceExpression) return@forEachDescendantOfType
|
||||
if (refExpr.parent is KtSuperExpression || refExpr.parent is KtThisExpression) return@forEachDescendantOfType
|
||||
if (refExpr.parentOfType<KtPackageDirective>() != null) return@forEachDescendantOfType
|
||||
val mainReference = refExpr.mainReference
|
||||
if (mainReference is KtConstructorDelegationReference) return@forEachDescendantOfType
|
||||
val resolved = mainReference.resolve() as? PsiNamedElement ?: return@forEachDescendantOfType
|
||||
refExpr.internalUsageInfo = Source(refExpr, mainReference, resolved, true)
|
||||
for (child in elem.children) {
|
||||
markInternalUsages(child, topLevelMoved)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtReferenceExpression.markInternalUsageInfo(topLevelMoved: KtElement) {
|
||||
val expr = this
|
||||
val mainReference = expr.mainReference
|
||||
if (expr is KtCallExpression && mainReference !is KtInvokeFunctionReference) return // to avoid duplication when handling name
|
||||
if (expr is KtEnumEntrySuperclassReferenceExpression) return
|
||||
val parent = expr.parent
|
||||
if (parent is KtSuperExpression || parent is KtThisExpression) return
|
||||
if (expr.parentOfType<KtPackageDirective>() != null) return
|
||||
if (expr.parentOfType<KtImportDirective>(withSelf = false) != null) return
|
||||
if (mainReference is KtConstructorDelegationReference) return
|
||||
val resolved = mainReference.resolve() as? PsiNamedElement ?: return
|
||||
val isExtensionReference = if (resolved is KtCallableDeclaration) {
|
||||
analyze(resolved) {
|
||||
val symbol = resolved.symbol
|
||||
if (symbol is KaCallableSymbol) expr.isExtensionReference(symbol) else false
|
||||
}
|
||||
} else false
|
||||
if (resolved is KtParameter) return
|
||||
if (resolved is KtNamedDeclaration && resolved.isDeclaredInContainingContext(expr, topLevelMoved)) return
|
||||
val usageInfo = Source(expr, mainReference, resolved, true)
|
||||
if (expr.isFirstReferenceInQualifiedChain() || isExtensionReference) {
|
||||
expr.updatableUsageInfo = usageInfo
|
||||
} else {
|
||||
expr.nonUpdatableUsageInfo = usageInfo
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtNamedDeclaration.isDeclaredInContainingContext(expr: KtReferenceExpression, topLevelMoved: KtElement): Boolean {
|
||||
return generateSequence<KtNamedDeclaration>(expr.parentOfType<KtNamedDeclaration>()) { containing ->
|
||||
if (containing == topLevelMoved) return@generateSequence null
|
||||
containing.parentOfType<KtNamedDeclaration>()
|
||||
}.firstOrNull { containing ->
|
||||
if (containing is KtDeclarationContainer) {
|
||||
containing.declarations.contains(this)
|
||||
} else false
|
||||
} != null
|
||||
}
|
||||
|
||||
context(KaSession)
|
||||
private fun KtReferenceExpression.isExtensionReference(symbol: KaCallableSymbol): Boolean {
|
||||
if (symbol.isExtension == true) return true
|
||||
return if (symbol is KaPropertySymbol) {
|
||||
val returnType = symbol.returnType
|
||||
returnType is KaFunctionType && returnType.receiverType != null
|
||||
} else false
|
||||
}
|
||||
|
||||
private fun KtReferenceExpression.isFirstReferenceInQualifiedChain(): Boolean {
|
||||
if (this is KtOperationReferenceExpression) return false // don't consider unary expressions like !foo()
|
||||
val qualifiedChain = getQualifiedChainElement().collectDescendantsOfType<KtReferenceExpression>()
|
||||
if (qualifiedChain.any { refExpr -> refExpr.updatableUsageInfo != null }) return false // chain is already covered
|
||||
for (refExpr in qualifiedChain) {
|
||||
val resolved = refExpr.mainReference.resolve()
|
||||
if (resolved !is PsiPackage) return this == refExpr
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun KtReferenceExpression.getQualifiedChainElement(): KtElement {
|
||||
return generateSequence<KtElement>(this) {
|
||||
it.parent as? KtQualifiedExpression ?: it.parent as? KtCallExpression ?: it.parent as? KtUserType
|
||||
}.last()
|
||||
}
|
||||
|
||||
fun unMarkAllUsages(containing: KtElement) = containing.forEachDescendantOfType<KtSimpleNameExpression> { refExpr ->
|
||||
refExpr.internalUsageInfo = null
|
||||
refExpr.updatableUsageInfo = null
|
||||
refExpr.nonUpdatableUsageInfo = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any internal usage infos that don't need to be updated.
|
||||
* In [markInternalUsages] we marked all internal usages, but some of these usages don't need to be updated.
|
||||
* Like, for example, instance methods.
|
||||
*/
|
||||
fun unMarkNonUpdatableUsages(topLevelMovedElements: Iterable<PsiElement>) {
|
||||
val allMovedDeclarations = topLevelMovedElements.flatMap { it.withChildDeclarations() }
|
||||
for (element in topLevelMovedElements) {
|
||||
element.forEachDescendantOfType<KtSimpleNameExpression> { refExpr ->
|
||||
val usageInfo = refExpr.internalUsageInfo ?: return@forEachDescendantOfType
|
||||
if (!usageInfo.isUpdatable(allMovedDeclarations)) refExpr.internalUsageInfo = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out usages that are not updatable, such usages might be needed for conflict checking but don't need to be touched during the
|
||||
* retargeting process.
|
||||
*/
|
||||
internal fun filterUpdatable(topLevelMovedElements: Iterable<PsiElement>, usages: Array<out UsageInfo>): List<UsageInfo> {
|
||||
val allMovedDeclarations = topLevelMovedElements.flatMap { it.withChildDeclarations() }
|
||||
return usages.filter { if (it is K2MoveRenameUsageInfo) it.isUpdatable(allMovedDeclarations) else true }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds usages to [declaration] excluding the usages inside [declaration].
|
||||
*/
|
||||
@@ -305,15 +279,16 @@ sealed class K2MoveRenameUsageInfo(
|
||||
}
|
||||
|
||||
internal fun retargetUsages(usages: List<K2MoveRenameUsageInfo>, oldToNewMap: Map<PsiElement, PsiElement>) {
|
||||
retargetInternalUsages(oldToNewMap)
|
||||
// Retarget external usages before internal usages to make sure imports in moved files are properly updated
|
||||
retargetExternalUsages(usages, oldToNewMap)
|
||||
retargetInternalUsages(oldToNewMap)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* [org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo]
|
||||
* After moving, internal usages might have become invalid, this method restores these usage infos.
|
||||
* @see internalUsageInfo
|
||||
* @see updatableUsageInfo
|
||||
*/
|
||||
private fun restoreInternalUsages(
|
||||
containingElem: KtElement,
|
||||
@@ -322,7 +297,7 @@ sealed class K2MoveRenameUsageInfo(
|
||||
): List<UsageInfo> {
|
||||
return (containingElem.collectDescendantsOfType<KDocName>() + containingElem.collectDescendantsOfType<KtReferenceExpression>())
|
||||
.mapNotNull { refExpr ->
|
||||
val usageInfo = refExpr.internalUsageInfo
|
||||
val usageInfo = refExpr.updatableUsageInfo
|
||||
if (!fromCopy && usageInfo?.element != null) return@mapNotNull usageInfo
|
||||
val referencedElement = (usageInfo as? Source)?.referencedElement ?: return@mapNotNull null
|
||||
val newReferencedElement = oldToNewMap[referencedElement] ?: referencedElement
|
||||
@@ -344,7 +319,7 @@ sealed class K2MoveRenameUsageInfo(
|
||||
val inCopy = fileCopy.collectDescendantsOfType<KtSimpleNameExpression>().filter(skipPackageStmt)
|
||||
val original = originalFile.collectDescendantsOfType<KtSimpleNameExpression>().filter(skipPackageStmt)
|
||||
val internalUsages = original.zip(inCopy).mapNotNull { (o, c) ->
|
||||
val usageInfo = o.internalUsageInfo
|
||||
val usageInfo = o.updatableUsageInfo
|
||||
val referencedElement = (usageInfo as? Source)?.referencedElement ?: return@mapNotNull null
|
||||
if (!referencedElement.isValid ||
|
||||
referencedElement !is PsiNamedElement ||
|
||||
|
||||
+3
-6
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.idea.base.projectStructure.toKaModule
|
||||
import org.jetbrains.kotlin.idea.base.util.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.idea.codeinsight.utils.toVisibility
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.internalUsageInfo
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveRenameUsageInfo.Companion.updatableUsageInfo
|
||||
import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.conflict.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -112,17 +113,13 @@ fun createCopyTarget(
|
||||
val oldToNewMap = declarationsToMove.moveInto(fakeTargetFile)
|
||||
val usageInfos = fakeTargetFile.collectOldToNewUsageInfos(oldToNewMap)
|
||||
usageInfos.forEach { (originalUsageInfo, copyUsageInfo) ->
|
||||
if (!originalUsageInfo.isUpdatable(oldToNewMap.values.toList())) {
|
||||
(copyUsageInfo.reference?.element as? KtReferenceExpression)?.internalUsageInfo = originalUsageInfo
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if ((originalUsageInfo.element as KtElement).updatableUsageInfo == null) return@forEach // if not updatable, skip
|
||||
// Retarget all references to make sure all references are resolvable after moving
|
||||
val retargetResult = copyUsageInfo.retarget(copyUsageInfo.referencedElement as PsiNamedElement) as? KtElement ?: return@forEach
|
||||
val retargetReference = retargetResult.getQualifiedElementSelector() as? KtReferenceExpression ?: return@forEach
|
||||
// Attach physical usage info to the copied reference.
|
||||
// This will make it possible for the conflict checker to check whether a conflict exists before even calling the refactoring.
|
||||
retargetReference.internalUsageInfo = originalUsageInfo
|
||||
retargetReference.updatableUsageInfo = originalUsageInfo
|
||||
}
|
||||
fakeTargetFile.originalFile = declarationsToMove.firstOrNull()?.containingKtFile ?: error("Moved element is not in a Kotlin file")
|
||||
return fakeTargetFile to oldToNewMap
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ internal fun KtFile.findUsages(
|
||||
searchForText: Boolean,
|
||||
newPkgName: FqName
|
||||
): List<UsageInfo> {
|
||||
markInternalUsages(this)
|
||||
markInternalUsages(this, this)
|
||||
return topLevelDeclarationsToUpdate.flatMap { decl ->
|
||||
K2MoveRenameUsageInfo.findExternalUsages(decl) + decl.findNonCodeUsages(searchInCommentsAndStrings, searchForText, newPkgName)
|
||||
}
|
||||
|
||||
+11
-8
@@ -30,6 +30,16 @@ abstract class AbstractK2MoveFileOrDirectoriesTest : AbstractMultifileMoveRefact
|
||||
internal object K2MoveFileOrDirectoriesRefactoringAction : KotlinMoveRefactoringAction {
|
||||
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, config: JsonObject) {
|
||||
val project = mainFile.project
|
||||
val fileNames = config.getAsJsonArray("filesToMove")?.map { it.asString }
|
||||
?: listOfNotNull(config.getString("mainFile"))
|
||||
val files = if (fileNames.isEmpty()) {
|
||||
listOf(mainFile)
|
||||
} else {
|
||||
fileNames.mapNotNull { path ->
|
||||
val vFile = rootDir.findFileByRelativePath(path)
|
||||
vFile?.toPsiFile(project) ?: vFile?.toPsiDirectory(project)
|
||||
}.toSet()
|
||||
}
|
||||
if (mainFile.name.endsWith(".java")) {
|
||||
val targetPackage = config.getNullableString("targetPackage")
|
||||
val targetDirPath = targetPackage?.replace('.', '/') ?: config.getNullableString("targetDirectory") ?: return
|
||||
@@ -40,7 +50,7 @@ internal object K2MoveFileOrDirectoriesRefactoringAction : KotlinMoveRefactoring
|
||||
}
|
||||
MoveFilesOrDirectoriesProcessor(
|
||||
project,
|
||||
arrayOf(mainFile),
|
||||
files.toTypedArray(),
|
||||
newParent,
|
||||
config.searchInComments(),
|
||||
/* searchInNonJavaFiles = */ true,
|
||||
@@ -48,13 +58,6 @@ internal object K2MoveFileOrDirectoriesRefactoringAction : KotlinMoveRefactoring
|
||||
/* prepareSuccessfulCallback = */ null
|
||||
).run()
|
||||
} else {
|
||||
val fileNames = config.getAsJsonArray("filesToMove")?.map { it.asString }
|
||||
?: listOfNotNull(config.getString("mainFile"))
|
||||
if (fileNames.isEmpty()) fail("No file name specified")
|
||||
val files = fileNames.mapNotNull { path ->
|
||||
val vFile = rootDir.findFileByRelativePath(path)
|
||||
vFile?.toPsiFile(project) ?: vFile?.toPsiDirectory(project)
|
||||
}.toSet()
|
||||
val sourceDescriptor = K2MoveSourceDescriptor.FileSource(files)
|
||||
val targetPackage = config.getNullableString("targetPackage")
|
||||
val targetDir = config.getNullableString("targetDirectory")
|
||||
|
||||
+5
@@ -40,6 +40,11 @@ public class K2MoveFileOrDirectoriesTestGenerated extends AbstractK2MoveFileOrDi
|
||||
runTest("../../idea/tests/testData/refactoring/moveFile/java/moveFileToAnotherPackage/moveFileToAnotherPackage.test");
|
||||
}
|
||||
|
||||
@TestMetadata("java/movePackageWithDestructuringReference/movePackageWithDestructuringReference.test")
|
||||
public void testJava_movePackageWithDestructuringReference_MovePackageWithDestructuringReference() throws Exception {
|
||||
runTest("../../idea/tests/testData/refactoring/moveFile/java/movePackageWithDestructuringReference/movePackageWithDestructuringReference.test");
|
||||
}
|
||||
|
||||
@TestMetadata("kotlin/addExtensionImport/addExtensionImport.test")
|
||||
public void testKotlin_addExtensionImport_AddExtensionImport() throws Exception {
|
||||
runTest("../../idea/tests/testData/refactoring/moveFile/kotlin/addExtensionImport/addExtensionImport.test");
|
||||
|
||||
Reference in New Issue
Block a user