Rename: Fixed rename of Java getters/setters through synthetic property references in Kotlin

#KT-8817 Fixed
This commit is contained in:
Alexey Sedunov
2016-04-19 19:01:04 +03:00
parent a45165838a
commit cc5c3c2353
27 changed files with 311 additions and 6 deletions
+1
View File
@@ -31,6 +31,7 @@ Issues fixed:
- [KT-11908](https://youtrack.jetbrains.com/issue/KT-11866) Allow properties with custom setters to be used in generated equals/hashCode/toString
- [KT-11845](https://youtrack.jetbrains.com/issue/KT-11845) Fixed exception on attempt to find derived classes
- [KT-11736](https://youtrack.jetbrains.com/issue/KT-11736) Fixed searching of Java usages for @JvmStatic properties and @JvmStatic @JvmOverloads functions
- [KT-8817](https://youtrack.jetbrains.com/issue/KT-8817) Fixed rename of Java getters/setters through synthetic property references in Kotlin
#### Debugger
@@ -51,6 +51,12 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre
override fun canRename() = true
fun renameByPropertyName(newName: String): PsiElement? {
val nameIdentifier = KtPsiFactory(expression).createNameIdentifier(newName)
expression.getReferencedNameElement().replace(nameIdentifier)
return expression
}
override fun handleElementRename(newElementName: String?): PsiElement? {
if (!Name.isValidIdentifier(newElementName!!)) return expression
@@ -65,9 +71,7 @@ sealed class SyntheticPropertyAccessorReference(expression: KtNameReferenceExpre
}
if (newName == null) return expression //TODO: handle the case when get/set becomes ordinary method
val nameIdentifier = KtPsiFactory(expression).createNameIdentifier(newName.identifier)
expression.getReferencedNameElement().replace(nameIdentifier)
return expression
return renameByPropertyName(newName.identifier)
}
class Getter(expression: KtNameReferenceExpression) : SyntheticPropertyAccessorReference(expression, true)
+3
View File
@@ -411,9 +411,12 @@
<renamePsiElementProcessor implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinFileProcessor"
id="KotlinFile"
order="first"/>
<renamePsiElementProcessor implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameJavaSyntheticPropertyHandler$Processor"
id="JavaSyntheticPropertyFromKotlin"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinImplicitLambdaParameter"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameDynamicMemberHandler"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameOnSecondaryConstructorHandler"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameJavaSyntheticPropertyHandler"/>
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableRenamerFactory"/>
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableRenamerFactoryForJavaClass"/>
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableInJavaRenamerFactory"/>
@@ -24,9 +24,12 @@ import com.intellij.psi.PsiNamedElement
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.refactoring.util.RefactoringDescriptionLocation
import com.intellij.usageView.UsageViewLongNameLocation
import com.intellij.usageView.UsageViewTypeLocation
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.refactoring.rename.RenameJavaSyntheticPropertyHandler
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -44,6 +47,7 @@ class KotlinElementDescriptionProvider : ElementDescriptionProvider {
is KtTypeParameter -> "type parameter"
is KtParameter -> "parameter"
is KtDestructuringDeclarationEntry -> "variable"
is RenameJavaSyntheticPropertyHandler.SyntheticPropertyWrapper -> "property"
else -> null
}
@@ -55,9 +59,9 @@ class KotlinElementDescriptionProvider : ElementDescriptionProvider {
return descriptor
}
if (targetElement !is PsiNamedElement || targetElement !is KtElement) return null
if (targetElement !is PsiNamedElement || targetElement.language != KotlinLanguage.INSTANCE) return null
return when(location) {
is UsageViewTypeLocation -> elementKind()
is UsageViewLongNameLocation -> targetElement.getName()
is RefactoringDescriptionLocation -> {
val kind = elementKind() ?: return null
@@ -0,0 +1,99 @@
/*
* Copyright 2010-2016 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.rename
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightElement
import com.intellij.psi.search.SearchScope
import com.intellij.refactoring.rename.PsiElementRenameHandler
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
class RenameJavaSyntheticPropertyHandler : PsiElementRenameHandler() {
class Processor : RenamePsiElementProcessor() {
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
val propertyWrapper = element as? SyntheticPropertyWrapper ?: return
propertyWrapper.getter?.let { allRenames[it] = JvmAbi.getterName(newName) }
propertyWrapper.setter?.let { allRenames[it] = JvmAbi.setterName(newName) }
}
override fun canProcessElement(element: PsiElement) = element is SyntheticPropertyWrapper
}
internal class SyntheticPropertyWrapper(
manager: PsiManager,
val descriptor: SyntheticJavaPropertyDescriptor
): LightElement(manager, KotlinLanguage.INSTANCE), PsiNamedElement {
val getter: PsiMethod? get() = descriptor.getMethod.source.getPsi() as? PsiMethod
val setter: PsiMethod? get() = descriptor.setMethod?.source?.getPsi() as? PsiMethod
override fun getContainingFile() = getter?.containingFile
override fun getName() = descriptor.name.asString()
override fun setName(name: String): PsiElement? {
getter?.name = JvmAbi.getterName(name)
setter?.name = JvmAbi.setterName(name)
return this
}
override fun toString(): String {
val renderer = IdeDescriptorRenderers.SOURCE_CODE
return "${renderer.render(descriptor.getMethod)}|${descriptor.setMethod?.let { renderer.render(it) }}"
}
}
private fun getTargetDescriptor(dataContext: DataContext): SyntheticJavaPropertyDescriptor? {
val caret = CommonDataKeys.CARET.getData(dataContext) ?: return null
val ktFile = CommonDataKeys.PSI_FILE.getData(dataContext) as? KtFile ?: return null
val refExpr = ktFile.findElementAt(caret.offset)?.getNonStrictParentOfType<KtSimpleNameExpression>() ?: return null
return refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] as? SyntheticJavaPropertyDescriptor
}
override fun isAvailableOnDataContext(dataContext: DataContext): Boolean {
return CommonDataKeys.EDITOR.getData(dataContext) != null && getTargetDescriptor(dataContext) != null
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?, dataContext: DataContext) {
val descriptor = getTargetDescriptor(dataContext) ?: return
val wrappingContext = DataContext { id ->
if (CommonDataKeys.PSI_ELEMENT.`is`(id)) {
return@DataContext SyntheticPropertyWrapper(PsiManager.getInstance(project), descriptor)
}
dataContext.getData(id)
}
super.invoke(project, editor, file, wrappingContext)
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext) {
// Can't be invoked outside of a text editor
}
}
@@ -0,0 +1,10 @@
public class Bean {
private boolean prop;
public boolean isProp2() { return prop; }
public void setProp2(boolean prop) { this.prop = prop; }
void test() {
isProp2();
setProp2(true);
}
}
@@ -0,0 +1,4 @@
fun test(bean: Bean) {
bean.isProp2 = true
println(bean.isProp2)
}
@@ -0,0 +1,10 @@
public class Bean {
private boolean prop;
public boolean isProp() { return prop; }
public void setProp(boolean prop) { this.prop = prop; }
void test() {
isProp();
setProp(true);
}
}
@@ -0,0 +1,4 @@
fun test(bean: Bean) {
bean.isProp = true
println(bean./*rename*/isProp)
}
@@ -0,0 +1,5 @@
{
"type": "SYNTHETIC_PROPERTY",
"mainFile": "test.kt",
"newName": "isProp2"
}
@@ -0,0 +1,10 @@
public class Bean {
private String prop = "value";
public String getProp2() { return prop; }
public void setProp2(String prop) { this.prop = prop; }
void test() {
getProp2();
setProp2("");
}
}
@@ -0,0 +1,5 @@
fun test(bean: Bean) {
bean.prop2 = "a"
println(bean.prop2)
bean.prop2 += "a"
}
@@ -0,0 +1,10 @@
public class Bean {
private String prop = "value";
public String getProp() { return prop; }
public void setProp(String prop) { this.prop = prop; }
void test() {
getProp();
setProp("");
}
}
@@ -0,0 +1,5 @@
fun test(bean: Bean) {
bean.prop = "a"
println(bean./*rename*/prop)
bean.prop += "a"
}
@@ -0,0 +1,5 @@
{
"type": "SYNTHETIC_PROPERTY",
"mainFile": "test.kt",
"newName": "prop2"
}
@@ -0,0 +1,10 @@
public class Bean {
private String prop = "value";
public String getProp2() { return prop; }
public void setProp2(String prop) { this.prop = prop; }
void test() {
getProp2();
setProp2("");
}
}
@@ -0,0 +1,5 @@
fun test(bean: Bean) {
bean.prop2 = "a"
println(bean.prop2)
bean.prop2 += "a"
}
@@ -0,0 +1,10 @@
public class Bean {
private String prop = "value";
public String getProp() { return prop; }
public void setProp(String prop) { this.prop = prop; }
void test() {
getProp();
setProp("");
}
}
@@ -0,0 +1,5 @@
fun test(bean: Bean) {
bean.prop = "a"
println(bean.prop)
bean./*rename*/prop += "a"
}
@@ -0,0 +1,5 @@
{
"type": "SYNTHETIC_PROPERTY",
"mainFile": "test.kt",
"newName": "prop2"
}
@@ -0,0 +1,10 @@
public class Bean {
private String prop = "value";
public String getProp2() { return prop; }
public void setProp2(String prop) { this.prop = prop; }
void test() {
getProp2();
setProp2("");
}
}
@@ -0,0 +1,5 @@
fun test(bean: Bean) {
bean.prop2 = "a"
println(bean.prop2)
bean.prop2 += "a"
}
@@ -0,0 +1,10 @@
public class Bean {
private String prop = "value";
public String getProp() { return prop; }
public void setProp(String prop) { this.prop = prop; }
void test() {
getProp();
setProp("");
}
}
@@ -0,0 +1,5 @@
fun test(bean: Bean) {
bean./*rename*/prop = "a"
println(bean.prop)
bean.prop += "a"
}
@@ -0,0 +1,5 @@
{
"type": "SYNTHETIC_PROPERTY",
"mainFile": "test.kt",
"newName": "prop2"
}
@@ -43,19 +43,25 @@ import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
import org.jetbrains.kotlin.idea.jsonUtils.getString
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.test.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.junit.Assert
import java.io.File
@@ -68,7 +74,8 @@ private enum class RenameType {
KOTLIN_PACKAGE,
MARKED_ELEMENT,
FILE,
BUNDLE_PROPERTY
BUNDLE_PROPERTY,
SYNTHETIC_PROPERTY
}
abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
@@ -109,6 +116,7 @@ abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
RenameType.MARKED_ELEMENT -> renameMarkedElement(renameObject, context)
RenameType.FILE -> renameFile(renameObject, context)
RenameType.BUNDLE_PROPERTY -> renameBundleProperty(renameObject, context)
RenameType.SYNTHETIC_PROPERTY -> renameSyntheticProperty(renameObject, context)
}
if (hintDirective != null) {
@@ -323,6 +331,30 @@ abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
}
}
private fun renameSyntheticProperty(renameParamsObject: JsonObject, context: TestContext) {
val mainFilePath = renameParamsObject.getString("mainFile")
val newName = renameParamsObject.getString("newName")
doTestCommittingDocuments { rootDir, rootAfter ->
configExtra(rootDir, renameParamsObject)
val psiFile = rootDir.findFileByRelativePath(mainFilePath)!!.toPsiFile(project)!!
val doc = PsiDocumentManager.getInstance(project).getDocument(psiFile)!!
val marker = doc.extractMarkerOffset(project, "/*rename*/")
assert(marker != -1)
val refExpr = psiFile.findElementAt(marker)!!.getNonStrictParentOfType<KtSimpleNameExpression>()!!
val descriptor = refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr]
as SyntheticJavaPropertyDescriptor
val propertyWrapper = RenameJavaSyntheticPropertyHandler.SyntheticPropertyWrapper(psiFile.manager, descriptor)
val substitution = RenamePsiElementProcessor.forElement(propertyWrapper).substituteElementToRename(propertyWrapper, null)
runRenameProcessor(context, newName, substitution, true, true)
}
}
private fun runRenameProcessor(
context: TestContext,
newName: String,
@@ -215,6 +215,30 @@ public class RenameTestGenerated extends AbstractRenameTest {
doTest(fileName);
}
@TestMetadata("renameJavaSyntheticIsPropertyByGetterRef/renameJavaSyntheticIsPropertyByGetterRef.test")
public void testRenameJavaSyntheticIsPropertyByGetterRef_RenameJavaSyntheticIsPropertyByGetterRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameJavaSyntheticIsPropertyByGetterRef/renameJavaSyntheticIsPropertyByGetterRef.test");
doTest(fileName);
}
@TestMetadata("renameJavaSyntheticPropertyByGetterRef/renameJavaSyntheticPropertyByGetterRef.test")
public void testRenameJavaSyntheticPropertyByGetterRef_RenameJavaSyntheticPropertyByGetterRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameJavaSyntheticPropertyByGetterRef/renameJavaSyntheticPropertyByGetterRef.test");
doTest(fileName);
}
@TestMetadata("renameJavaSyntheticPropertyByGetterSetterRef/renameJavaSyntheticPropertyByGetterSetterRef.test")
public void testRenameJavaSyntheticPropertyByGetterSetterRef_RenameJavaSyntheticPropertyByGetterSetterRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameJavaSyntheticPropertyByGetterSetterRef/renameJavaSyntheticPropertyByGetterSetterRef.test");
doTest(fileName);
}
@TestMetadata("renameJavaSyntheticPropertyBySetterRef/renameJavaSyntheticPropertyBySetterRef.test")
public void testRenameJavaSyntheticPropertyBySetterRef_RenameJavaSyntheticPropertyBySetterRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameJavaSyntheticPropertyBySetterRef/renameJavaSyntheticPropertyBySetterRef.test");
doTest(fileName);
}
@TestMetadata("renameKotlinBaseMethod/javaWrapperForBaseFunction.test")
public void testRenameKotlinBaseMethod_JavaWrapperForBaseFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameKotlinBaseMethod/javaWrapperForBaseFunction.test");