Rename: Support renaming class by short reference to companion object

#KT-16108 Fixed
This commit is contained in:
Alexey Sedunov
2017-02-16 15:22:01 +03:00
parent 834cdd63ab
commit 871d42f05a
9 changed files with 184 additions and 30 deletions
+1
View File
@@ -473,6 +473,7 @@
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameJvmNameHandler"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.KotlinDirectoryAsPackageRenameHandler"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameBackingFieldReferenceHandler"/>
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameClassByCompanionObjectShortReferenceHandler"/>
<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"/>
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2017 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.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.rename.PsiElementRenameHandler
import com.intellij.refactoring.rename.inplace.MemberInplaceRenameHandler
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
abstract class AbstractReferenceSubstitutionRenameHandler<D : DeclarationDescriptor> : PsiElementRenameHandler() {
private val inplaceRenameHandler = MemberInplaceRenameHandler()
protected fun getReferenceExpression(dataContext: DataContext): KtSimpleNameExpression? {
val caret = CommonDataKeys.CARET.getData(dataContext) ?: return null
val ktFile = CommonDataKeys.PSI_FILE.getData(dataContext) as? KtFile ?: return null
return ktFile.findElementAt(caret.offset)?.getNonStrictParentOfType<KtSimpleNameExpression>() ?: return null
}
protected abstract fun getTargetDescriptor(dataContext: DataContext): D?
protected open fun getElementToRename(project: Project, descriptor: D): PsiElement? {
return DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor)
}
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 getElementToRename(project, descriptor)
dataContext.getData(id)
}
// Can't provide new name for inplace refactoring in unit test mode
if (!ApplicationManager.getApplication().isUnitTestMode && inplaceRenameHandler.isAvailableOnDataContext(wrappingContext)) {
inplaceRenameHandler.invoke(project, editor, file, wrappingContext)
}
else {
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,30 @@
/*
* Copyright 2010-2017 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.DataContext
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class RenameClassByCompanionObjectShortReferenceHandler : AbstractReferenceSubstitutionRenameHandler<ClassifierDescriptor>() {
override fun getTargetDescriptor(dataContext: DataContext): ClassifierDescriptor? {
val refExpr = getReferenceExpression(dataContext) ?: return null
return refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, refExpr]
}
}
@@ -16,28 +16,25 @@
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.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiNamedElement
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 RenameJavaSyntheticPropertyHandler : AbstractReferenceSubstitutionRenameHandler<SyntheticJavaPropertyDescriptor>() {
class Processor : RenamePsiElementProcessor() {
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
val propertyWrapper = element as? SyntheticPropertyWrapper ?: return
@@ -71,29 +68,12 @@ class RenameJavaSyntheticPropertyHandler : PsiElementRenameHandler() {
}
}
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
override fun getTargetDescriptor(dataContext: DataContext): SyntheticJavaPropertyDescriptor? {
val refExpr = getReferenceExpression(dataContext) ?: 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
override fun getElementToRename(project: Project, descriptor: SyntheticJavaPropertyDescriptor): PsiElement? {
return SyntheticPropertyWrapper(PsiManager.getInstance(project), descriptor)
}
}
@@ -0,0 +1,9 @@
class Bar {
companion object {
val VALUE = 42
}
}
fun main(args: Array<String>) {
println(Bar.VALUE)
}
@@ -0,0 +1,9 @@
class Foo {
companion object {
val VALUE = 42
}
}
fun main(args: Array<String>) {
println(/*rename*/Foo.VALUE)
}
@@ -0,0 +1,5 @@
{
"type": "SHORT_COMPANION_REFERENCE",
"mainFile": "test.kt",
"newName": "Bar"
}
@@ -21,7 +21,11 @@ import com.google.gson.JsonParser
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.lang.properties.psi.PropertiesFile
import com.intellij.lang.properties.psi.Property
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.impl.CaretImpl
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.module.Module
@@ -37,6 +41,7 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.MultiFileTestCase
import com.intellij.refactoring.rename.PsiElementRenameHandler
import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
@@ -76,7 +81,8 @@ private enum class RenameType {
MARKED_ELEMENT,
FILE,
BUNDLE_PROPERTY,
SYNTHETIC_PROPERTY
SYNTHETIC_PROPERTY,
SHORT_COMPANION_REFERENCE
}
abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
@@ -123,6 +129,7 @@ abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
RenameType.FILE -> renameFile(renameObject, context)
RenameType.BUNDLE_PROPERTY -> renameBundleProperty(renameObject, context)
RenameType.SYNTHETIC_PROPERTY -> renameSyntheticProperty(renameObject, context)
RenameType.SHORT_COMPANION_REFERENCE -> renameShortCompanionReference(renameObject, context)
}
if (hintDirective != null) {
@@ -364,6 +371,42 @@ abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
}
}
private fun renameShortCompanionReference(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 editor = EditorFactory.getInstance().createEditor(doc)
editor.caretModel.moveToOffset(marker)
try {
val handler = RenameClassByCompanionObjectShortReferenceHandler()
val dataContext = DataContext { dataId ->
when (dataId) {
CommonDataKeys.EDITOR.name -> editor
CommonDataKeys.CARET.name -> editor.caretModel.currentCaret
CommonDataKeys.PSI_FILE.name -> psiFile
PsiElementRenameHandler.DEFAULT_NAME.name -> newName
else -> null
}
}
Assert.assertTrue(handler.isAvailableOnDataContext(dataContext))
handler.invoke(project, editor, psiFile, dataContext)
}
finally {
EditorFactory.getInstance().releaseEditor(editor)
}
}
}
private fun runRenameProcessor(
context: TestContext,
newName: String,
@@ -192,6 +192,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
doTest(fileName);
}
@TestMetadata("companionShortRef/companionShortRef.test")
public void testCompanionShortRef_CompanionShortRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/companionShortRef/companionShortRef.test");
doTest(fileName);
}
@TestMetadata("fileNotUnderSourceRootWithNamesakeUnderSourceRoot/fileNotUnderSourceRootWithNamesakeUnderSourceRoot.test")
public void testFileNotUnderSourceRootWithNamesakeUnderSourceRoot_FileNotUnderSourceRootWithNamesakeUnderSourceRoot() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/fileNotUnderSourceRootWithNamesakeUnderSourceRoot/fileNotUnderSourceRootWithNamesakeUnderSourceRoot.test");