RenameKotlinPropertyProcessor made not update the property before all references are updated (KT-27602)

because otherwise language independent bean property references couldn't properly handle rename in some cases.

Groovy references were also affected by this bug because they weren't able to resolve to original property
This commit is contained in:
Nicolay Mitropolsky
2019-02-12 21:17:58 +03:00
parent 2bcdadf17f
commit 48619a49ed
3 changed files with 133 additions and 11 deletions
@@ -36,9 +36,7 @@ import org.jetbrains.kotlin.asJava.*
import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.demangleInternalName import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.*
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.getModuleNameSuffix
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.mangleInternalName
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
@@ -350,6 +348,7 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
SETTER_USAGE SETTER_USAGE
} }
//TODO: a very long and complicated method, even recursive. mb refactor it somehow? at least split by PsiElement types?
override tailrec fun renameElement(element: PsiElement, newName: String, usages: Array<UsageInfo>, listener: RefactoringElementListener?) { override tailrec fun renameElement(element: PsiElement, newName: String, usages: Array<UsageInfo>, listener: RefactoringElementListener?) {
val newNameUnquoted = newName.unquote() val newNameUnquoted = newName.unquote()
if (element is KtLightMethod) { if (element is KtLightMethod) {
@@ -405,17 +404,16 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
oldSetterName -> UsageKind.SETTER_USAGE oldSetterName -> UsageKind.SETTER_USAGE
else -> UsageKind.SIMPLE_PROPERTY_USAGE else -> UsageKind.SIMPLE_PROPERTY_USAGE
} }
} } else {
else {
UsageKind.SIMPLE_PROPERTY_USAGE UsageKind.SIMPLE_PROPERTY_USAGE
} }
} }
super.renameElement(element, JvmAbi.setterName(newNameUnquoted).quoteIfNeeded(), super.renameElement(element.copy(), JvmAbi.setterName(newNameUnquoted).quoteIfNeeded(),
refKindUsages[UsageKind.SETTER_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(), refKindUsages[UsageKind.SETTER_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(),
null) null)
super.renameElement(element, JvmAbi.getterName(newNameUnquoted).quoteIfNeeded(), super.renameElement(element.copy(), JvmAbi.getterName(newNameUnquoted).quoteIfNeeded(),
refKindUsages[UsageKind.GETTER_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(), refKindUsages[UsageKind.GETTER_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(),
null) null)
@@ -4,11 +4,11 @@ import testing.rename.*;
class GroovyClient { class GroovyClient {
public void foo(AP ap, DP dp) { public void foo(AP ap, DP dp) {
ap.getSecond ap.second
new BP().getSecond new BP().second
new CP().getSecond new CP().second
dp.getSecond dp.second
new EP().second new EP().second
new FP().second new FP().second
} }
@@ -0,0 +1,124 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.uast.test.kotlin
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.util.Disposer
import com.intellij.patterns.uast.literalExpression
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.reference.PsiReferenceContributorEP
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl
import com.intellij.psi.util.PropertyUtil
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.registerServiceInstance
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.evaluateString
import org.jetbrains.uast.toUElementOfType
import org.junit.Test
import kotlin.test.fail
class KotlinUastReferencesTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
@Test
fun `test original getter is visible when reference is under renaming`() {
registerReferenceProviders(testRootDisposable) {
registerUastReferenceProvider(literalExpression(), uastLiteralReferenceProvider { _, psiLanguageInjectionHost ->
arrayOf(GetterReference("KotlinBean", psiLanguageInjectionHost))
})
}
myFixture.configureByText(
"KotlinBean.kt", """
data class KotlinBean(val myF<caret>ield: String)
val reference = "myField"
""".trimIndent()
)
myFixture.renameElementAtCaret("myRenamedField")
myFixture.checkResult(
"""
data class KotlinBean(val myRenamedField: String)
val reference = "myRenamedField"
""".trimIndent()
)
}
}
private class GetterReference(
val className: String,
psiElement: PsiElement
) : PsiReferenceBase<PsiElement>(psiElement) {
override fun resolve(): PsiMethod? {
val psiClass = JavaPsiFacade.getInstance(element.project).findClass(className, element.resolveScope) ?: return null
val name = element.toUElementOfType<UExpression>()?.evaluateString() ?: return null
return PropertyUtil.getGetters(psiClass, name).firstOrNull()
}
override fun handleElementRename(newElementName: String): PsiElement {
val resolve = resolve()
?: fail("can't resolve during rename, looks like someone renamed or removed the source element before updating references")
val newName =
if (PropertyUtil.getPropertyName(resolve) != null)
PropertyUtil.getPropertyName(newElementName) ?: newElementName
else newElementName
return super.handleElementRename(newName)
}
override fun getVariants(): Array<Any> = emptyArray()
}
fun registerReferenceProviders(disposable: Disposable, registerContributors: PsiReferenceRegistrar.() -> Unit) {
registerReferenceContributor(object : PsiReferenceContributor() {
override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) = registrar.registerContributors()
}, disposable)
}
fun registerReferenceContributor(contributor: PsiReferenceContributor, disposable: Disposable) {
val referenceContributorEp = Extensions.getArea(null).getExtensionPoint<PsiReferenceContributorEP>(PsiReferenceContributor.EP_NAME.name)
val contributorEp = object : PsiReferenceContributorEP() {
override fun getInstance(): PsiReferenceContributor = contributor
}
referenceContributorEp.registerExtension(contributorEp)
val application = ApplicationManager.getApplication()
//we need a fresh ReferenceProvidersRegistry after updating ReferenceContributors
val oldReferenceProviderRegistry =
application.picoContainer.getComponentInstance(ReferenceProvidersRegistry::class.java) as ReferenceProvidersRegistry
application.registerServiceInstance(ReferenceProvidersRegistry::class.java, ReferenceProvidersRegistryImpl())
Disposer.register(disposable, Disposable {
referenceContributorEp.unregisterExtension(contributorEp)
application.registerServiceInstance(ReferenceProvidersRegistry::class.java, oldReferenceProviderRegistry)
})
}