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.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"/>
|
||||
|
||||
+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
|
||||
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user