From 48619a49ed5d09ce2b4abe126ed73bd37a04bbbd Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Tue, 12 Feb 2019 21:17:58 +0300 Subject: [PATCH] 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 --- .../rename/RenameKotlinPropertyProcessor.kt | 12 +- .../after/testing/GroovyClient.groovy | 8 +- .../tests/KotlinUastReferencesTest.kt | 124 ++++++++++++++++++ 3 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 plugins/uast-kotlin/tests/KotlinUastReferencesTest.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt index 7d4d871568f..db63fab55b1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt @@ -36,9 +36,7 @@ import org.jetbrains.kotlin.asJava.* import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration import org.jetbrains.kotlin.asJava.elements.KtLightMethod -import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.demangleInternalName -import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.getModuleNameSuffix -import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.mangleInternalName +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper.InternalNameMapper.* import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor @@ -350,6 +348,7 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() { 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, listener: RefactoringElementListener?) { val newNameUnquoted = newName.unquote() if (element is KtLightMethod) { @@ -405,17 +404,16 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() { oldSetterName -> UsageKind.SETTER_USAGE else -> UsageKind.SIMPLE_PROPERTY_USAGE } - } - else { + } else { 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(), null) - super.renameElement(element, JvmAbi.getterName(newNameUnquoted).quoteIfNeeded(), + super.renameElement(element.copy(), JvmAbi.getterName(newNameUnquoted).quoteIfNeeded(), refKindUsages[UsageKind.GETTER_USAGE]?.toTypedArray() ?: arrayOf(), null) diff --git a/idea/testData/refactoring/rename/renameKotlinValProperty/after/testing/GroovyClient.groovy b/idea/testData/refactoring/rename/renameKotlinValProperty/after/testing/GroovyClient.groovy index 3b5e23de5cf..98d0b15b5fb 100644 --- a/idea/testData/refactoring/rename/renameKotlinValProperty/after/testing/GroovyClient.groovy +++ b/idea/testData/refactoring/rename/renameKotlinValProperty/after/testing/GroovyClient.groovy @@ -4,11 +4,11 @@ import testing.rename.*; class GroovyClient { public void foo(AP ap, DP dp) { - ap.getSecond - new BP().getSecond - new CP().getSecond + ap.second + new BP().second + new CP().second - dp.getSecond + dp.second new EP().second new FP().second } diff --git a/plugins/uast-kotlin/tests/KotlinUastReferencesTest.kt b/plugins/uast-kotlin/tests/KotlinUastReferencesTest.kt new file mode 100644 index 00000000000..a2729463164 --- /dev/null +++ b/plugins/uast-kotlin/tests/KotlinUastReferencesTest.kt @@ -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 myField: 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) { + override fun resolve(): PsiMethod? { + val psiClass = JavaPsiFacade.getInstance(element.project).findClass(className, element.resolveScope) ?: return null + val name = element.toUElementOfType()?.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 = 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(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) + }) + +} + + + +