Rename: Support renaming class by short reference to companion object
#KT-16108 Fixed
This commit is contained in:
@@ -473,6 +473,7 @@
|
|||||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameJvmNameHandler"/>
|
<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.KotlinDirectoryAsPackageRenameHandler"/>
|
||||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameBackingFieldReferenceHandler"/>
|
<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.AutomaticVariableRenamerFactory"/>
|
||||||
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableRenamerFactoryForJavaClass"/>
|
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableRenamerFactoryForJavaClass"/>
|
||||||
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableInJavaRenamerFactory"/>
|
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableInJavaRenamerFactory"/>
|
||||||
|
|||||||
+71
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-29
@@ -16,28 +16,25 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.rename
|
package org.jetbrains.kotlin.idea.refactoring.rename
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.editor.Editor
|
|
||||||
import com.intellij.openapi.project.Project
|
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.impl.light.LightElement
|
||||||
import com.intellij.psi.search.SearchScope
|
import com.intellij.psi.search.SearchScope
|
||||||
import com.intellij.refactoring.rename.PsiElementRenameHandler
|
|
||||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
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.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
||||||
|
|
||||||
class RenameJavaSyntheticPropertyHandler : PsiElementRenameHandler() {
|
class RenameJavaSyntheticPropertyHandler : AbstractReferenceSubstitutionRenameHandler<SyntheticJavaPropertyDescriptor>() {
|
||||||
class Processor : RenamePsiElementProcessor() {
|
class Processor : RenamePsiElementProcessor() {
|
||||||
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
|
||||||
val propertyWrapper = element as? SyntheticPropertyWrapper ?: return
|
val propertyWrapper = element as? SyntheticPropertyWrapper ?: return
|
||||||
@@ -71,29 +68,12 @@ class RenameJavaSyntheticPropertyHandler : PsiElementRenameHandler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTargetDescriptor(dataContext: DataContext): SyntheticJavaPropertyDescriptor? {
|
override fun getTargetDescriptor(dataContext: DataContext): SyntheticJavaPropertyDescriptor? {
|
||||||
val caret = CommonDataKeys.CARET.getData(dataContext) ?: return null
|
val refExpr = getReferenceExpression(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
|
return refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] as? SyntheticJavaPropertyDescriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isAvailableOnDataContext(dataContext: DataContext): Boolean {
|
override fun getElementToRename(project: Project, descriptor: SyntheticJavaPropertyDescriptor): PsiElement? {
|
||||||
return CommonDataKeys.EDITOR.getData(dataContext) != null && getTargetDescriptor(dataContext) != null
|
return SyntheticPropertyWrapper(PsiManager.getInstance(project), descriptor)
|
||||||
}
|
|
||||||
|
|
||||||
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,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.codeInsight.TargetElementUtil
|
||||||
import com.intellij.lang.properties.psi.PropertiesFile
|
import com.intellij.lang.properties.psi.PropertiesFile
|
||||||
import com.intellij.lang.properties.psi.Property
|
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.Editor
|
||||||
|
import com.intellij.openapi.editor.EditorFactory
|
||||||
|
import com.intellij.openapi.editor.impl.CaretImpl
|
||||||
import com.intellij.openapi.extensions.Extensions
|
import com.intellij.openapi.extensions.Extensions
|
||||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
import com.intellij.openapi.module.Module
|
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.psi.search.GlobalSearchScope
|
||||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||||
import com.intellij.refactoring.MultiFileTestCase
|
import com.intellij.refactoring.MultiFileTestCase
|
||||||
|
import com.intellij.refactoring.rename.PsiElementRenameHandler
|
||||||
import com.intellij.refactoring.rename.RenameProcessor
|
import com.intellij.refactoring.rename.RenameProcessor
|
||||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
||||||
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
|
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
|
||||||
@@ -76,7 +81,8 @@ private enum class RenameType {
|
|||||||
MARKED_ELEMENT,
|
MARKED_ELEMENT,
|
||||||
FILE,
|
FILE,
|
||||||
BUNDLE_PROPERTY,
|
BUNDLE_PROPERTY,
|
||||||
SYNTHETIC_PROPERTY
|
SYNTHETIC_PROPERTY,
|
||||||
|
SHORT_COMPANION_REFERENCE
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
||||||
@@ -123,6 +129,7 @@ abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
|||||||
RenameType.FILE -> renameFile(renameObject, context)
|
RenameType.FILE -> renameFile(renameObject, context)
|
||||||
RenameType.BUNDLE_PROPERTY -> renameBundleProperty(renameObject, context)
|
RenameType.BUNDLE_PROPERTY -> renameBundleProperty(renameObject, context)
|
||||||
RenameType.SYNTHETIC_PROPERTY -> renameSyntheticProperty(renameObject, context)
|
RenameType.SYNTHETIC_PROPERTY -> renameSyntheticProperty(renameObject, context)
|
||||||
|
RenameType.SHORT_COMPANION_REFERENCE -> renameShortCompanionReference(renameObject, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hintDirective != null) {
|
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(
|
private fun runRenameProcessor(
|
||||||
context: TestContext,
|
context: TestContext,
|
||||||
newName: String,
|
newName: String,
|
||||||
|
|||||||
@@ -192,6 +192,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("fileNotUnderSourceRootWithNamesakeUnderSourceRoot/fileNotUnderSourceRootWithNamesakeUnderSourceRoot.test")
|
||||||
public void testFileNotUnderSourceRootWithNamesakeUnderSourceRoot_FileNotUnderSourceRootWithNamesakeUnderSourceRoot() throws Exception {
|
public void testFileNotUnderSourceRootWithNamesakeUnderSourceRoot_FileNotUnderSourceRootWithNamesakeUnderSourceRoot() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/fileNotUnderSourceRootWithNamesakeUnderSourceRoot/fileNotUnderSourceRootWithNamesakeUnderSourceRoot.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/fileNotUnderSourceRootWithNamesakeUnderSourceRoot/fileNotUnderSourceRootWithNamesakeUnderSourceRoot.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user