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.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"/>
@@ -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 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");