From 871d42f05a4c245e10a552979b61202db4b53413 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 16 Feb 2017 15:22:01 +0300 Subject: [PATCH] Rename: Support renaming class by short reference to companion object #KT-16108 Fixed --- idea/src/META-INF/plugin.xml | 1 + ...tractReferenceSubstitutionRenameHandler.kt | 71 +++++++++++++++++++ ...sByCompanionObjectShortReferenceHandler.kt | 30 ++++++++ .../RenameJavaSyntheticPropertyHandler.kt | 38 +++------- .../rename/companionShortRef/after/test.kt | 9 +++ .../rename/companionShortRef/before/test.kt | 9 +++ .../companionShortRef/companionShortRef.test | 5 ++ .../refactoring/rename/AbstractRenameTest.kt | 45 +++++++++++- .../rename/RenameTestGenerated.java | 6 ++ 9 files changed, 184 insertions(+), 30 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/rename/AbstractReferenceSubstitutionRenameHandler.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameClassByCompanionObjectShortReferenceHandler.kt create mode 100644 idea/testData/refactoring/rename/companionShortRef/after/test.kt create mode 100644 idea/testData/refactoring/rename/companionShortRef/before/test.kt create mode 100644 idea/testData/refactoring/rename/companionShortRef/companionShortRef.test diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index f4b715320e7..3baab4bd4ef 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -473,6 +473,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/AbstractReferenceSubstitutionRenameHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/AbstractReferenceSubstitutionRenameHandler.kt new file mode 100644 index 00000000000..3d4a0fdecd7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/AbstractReferenceSubstitutionRenameHandler.kt @@ -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 : 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() ?: 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, dataContext: DataContext) { + // Can't be invoked outside of a text editor + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameClassByCompanionObjectShortReferenceHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameClassByCompanionObjectShortReferenceHandler.kt new file mode 100644 index 00000000000..d2fc0b712be --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameClassByCompanionObjectShortReferenceHandler.kt @@ -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() { + override fun getTargetDescriptor(dataContext: DataContext): ClassifierDescriptor? { + val refExpr = getReferenceExpression(dataContext) ?: return null + return refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, refExpr] + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameJavaSyntheticPropertyHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameJavaSyntheticPropertyHandler.kt index 1cd77db0f9e..9ed37119039 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameJavaSyntheticPropertyHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameJavaSyntheticPropertyHandler.kt @@ -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() { class Processor : RenamePsiElementProcessor() { override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap, 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() ?: 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, 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) } } diff --git a/idea/testData/refactoring/rename/companionShortRef/after/test.kt b/idea/testData/refactoring/rename/companionShortRef/after/test.kt new file mode 100644 index 00000000000..0a904c6cedd --- /dev/null +++ b/idea/testData/refactoring/rename/companionShortRef/after/test.kt @@ -0,0 +1,9 @@ +class Bar { + companion object { + val VALUE = 42 + } +} + +fun main(args: Array) { + println(Bar.VALUE) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/companionShortRef/before/test.kt b/idea/testData/refactoring/rename/companionShortRef/before/test.kt new file mode 100644 index 00000000000..d84c8a57870 --- /dev/null +++ b/idea/testData/refactoring/rename/companionShortRef/before/test.kt @@ -0,0 +1,9 @@ +class Foo { + companion object { + val VALUE = 42 + } +} + +fun main(args: Array) { + println(/*rename*/Foo.VALUE) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/companionShortRef/companionShortRef.test b/idea/testData/refactoring/rename/companionShortRef/companionShortRef.test new file mode 100644 index 00000000000..4adb4f124c1 --- /dev/null +++ b/idea/testData/refactoring/rename/companionShortRef/companionShortRef.test @@ -0,0 +1,5 @@ +{ + "type": "SHORT_COMPANION_REFERENCE", + "mainFile": "test.kt", + "newName": "Bar" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt index 7d7c16cf9e6..73fc462ae95 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/AbstractRenameTest.kt @@ -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, diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java index 79af336d421..5b0c4952b76 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java @@ -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");