diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 44c0f6f6c31..9fe69ed6b9c 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -516,6 +516,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 index 2859706fc03..f4cd44583a3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/AbstractReferenceSubstitutionRenameHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/AbstractReferenceSubstitutionRenameHandler.kt @@ -35,14 +35,20 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType abstract class AbstractReferenceSubstitutionRenameHandler( private val delegateHandler: RenameHandler = MemberInplaceRenameHandler() ) : PsiElementRenameHandler() { - 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 - var elementAtCaret = ktFile.findElementAt(caret.offset) - if (elementAtCaret is PsiWhiteSpace) { - elementAtCaret = CodeInsightUtils.getElementAtOffsetIgnoreWhitespaceAfter(ktFile, caret.offset) + companion object { + fun getReferenceExpression(file: PsiFile, offset: Int): KtSimpleNameExpression? { + var elementAtCaret = file.findElementAt(offset) + if (elementAtCaret is PsiWhiteSpace) { + elementAtCaret = CodeInsightUtils.getElementAtOffsetIgnoreWhitespaceAfter(file, offset) + } + return elementAtCaret?.getNonStrictParentOfType() + } + + 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 getReferenceExpression(ktFile, caret.offset) } - return elementAtCaret?.getNonStrictParentOfType() } protected abstract fun getElementToRename(dataContext: DataContext): PsiElement? diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt new file mode 100644 index 00000000000..64afe17f98e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinMemberInplaceRenameHandler.kt @@ -0,0 +1,69 @@ +/* + * 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.editor.Editor +import com.intellij.openapi.util.Comparing +import com.intellij.psi.* +import com.intellij.refactoring.rename.inplace.MemberInplaceRenameHandler +import com.intellij.refactoring.rename.inplace.MemberInplaceRenamer +import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.unquote +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtLabelReferenceExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class KotlinMemberInplaceRenameHandler : MemberInplaceRenameHandler() { + private class RenamerImpl( + elementToRename: PsiNamedElement, + substitutedElement: PsiElement?, + editor: Editor, + currentName: String, + oldName: String + ) : MemberInplaceRenamer(elementToRename, substitutedElement, editor, currentName, oldName) { + override fun acceptReference(reference: PsiReference): Boolean { + val refElement = reference.element + val textRange = reference.rangeInElement + val referenceText = refElement.text.substring(textRange.startOffset, textRange.endOffset).unquote() + return referenceText == myElementToRename.name + } + + override fun createInplaceRenamerToRestart(variable: PsiNamedElement, editor: Editor, initialName: String): VariableInplaceRenamer { + return RenamerImpl(variable, substituted, editor, initialName, myOldName) + } + } + + override fun createMemberRenamer(element: PsiElement, elementToRename: PsiNameIdentifierOwner, editor: Editor): MemberInplaceRenamer { + val currentName = elementToRename.nameIdentifier?.text ?: "" + return RenamerImpl(elementToRename, element, editor, currentName, currentName) + } + + override fun isAvailable(element: PsiElement?, editor: Editor, file: PsiFile): Boolean { + if (element !is KtElement) return false + if (!super.isAvailable(element, editor, file)) return false + + val referenceExpression = AbstractReferenceSubstitutionRenameHandler.getReferenceExpression(file, editor.caretModel.offset) ?: return true + if (referenceExpression is KtLabelReferenceExpression) return false + if (referenceExpression.mainReference.getImportAlias() != null) return false + if (referenceExpression.analyze(BodyResolveMode.PARTIAL)[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, referenceExpression] != null) return false + + return true + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt index 133d9d963e9..3c32a84ed5c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension @@ -112,7 +113,10 @@ class KotlinTargetElementEvaluator : TargetElementEvaluatorEx, TargetElementUtil } override fun isIdentifierPart(file: PsiFile, text: CharSequence?, offset: Int): Boolean { + val elementAtCaret = file.findElementAt(offset) + + if (elementAtCaret?.node?.elementType == KtTokens.IDENTIFIER) return true // '(' is considered identifier part if it belongs to primary constructor without 'constructor' keyword - return file.findElementAt(offset)?.getNonStrictParentOfType()?.textOffset == offset + return elementAtCaret?.getNonStrictParentOfType()?.textOffset == offset } } diff --git a/idea/testData/refactoring/rename/nonQuotedToQuoted/after/test.kt b/idea/testData/refactoring/rename/nonQuotedToQuoted/after/test.kt new file mode 100644 index 00000000000..42ba390f4ed --- /dev/null +++ b/idea/testData/refactoring/rename/nonQuotedToQuoted/after/test.kt @@ -0,0 +1,8 @@ +fun `fun`(n: Int) { + +} + +fun test() { + `fun`(1) + `fun`(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/nonQuotedToQuoted/before/test.kt b/idea/testData/refactoring/rename/nonQuotedToQuoted/before/test.kt new file mode 100644 index 00000000000..27a5ee0a9e8 --- /dev/null +++ b/idea/testData/refactoring/rename/nonQuotedToQuoted/before/test.kt @@ -0,0 +1,8 @@ +fun /*rename*/foo(n: Int) { + +} + +fun test() { + foo(1) + foo(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/nonQuotedToQuoted/nonQuotedToQuoted.test b/idea/testData/refactoring/rename/nonQuotedToQuoted/nonQuotedToQuoted.test new file mode 100644 index 00000000000..3125d1ea960 --- /dev/null +++ b/idea/testData/refactoring/rename/nonQuotedToQuoted/nonQuotedToQuoted.test @@ -0,0 +1,5 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test.kt", + "newName": "`fun`" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/quotedToNonQuoted/after/test.kt b/idea/testData/refactoring/rename/quotedToNonQuoted/after/test.kt new file mode 100644 index 00000000000..b701ef3f2dc --- /dev/null +++ b/idea/testData/refactoring/rename/quotedToNonQuoted/after/test.kt @@ -0,0 +1,8 @@ +fun foo(n: Int) { + +} + +fun test() { + foo(1) + foo(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/quotedToNonQuoted/before/test.kt b/idea/testData/refactoring/rename/quotedToNonQuoted/before/test.kt new file mode 100644 index 00000000000..2e95027d932 --- /dev/null +++ b/idea/testData/refactoring/rename/quotedToNonQuoted/before/test.kt @@ -0,0 +1,8 @@ +fun /*rename*/`fun`(n: Int) { + +} + +fun test() { + `fun`(1) + `fun`(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/quotedToNonQuoted/quotedToNonQuoted.test b/idea/testData/refactoring/rename/quotedToNonQuoted/quotedToNonQuoted.test new file mode 100644 index 00000000000..0a3c57ce475 --- /dev/null +++ b/idea/testData/refactoring/rename/quotedToNonQuoted/quotedToNonQuoted.test @@ -0,0 +1,5 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test.kt", + "newName": "foo" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/after/test.kt b/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/after/test.kt new file mode 100644 index 00000000000..b701ef3f2dc --- /dev/null +++ b/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/after/test.kt @@ -0,0 +1,8 @@ +fun foo(n: Int) { + +} + +fun test() { + foo(1) + foo(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/before/test.kt b/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/before/test.kt new file mode 100644 index 00000000000..378fdbd3199 --- /dev/null +++ b/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/before/test.kt @@ -0,0 +1,8 @@ +fun `fun`/*rename*/(n: Int) { + +} + +fun test() { + `fun`(1) + `fun`(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/quotedToNonQuotedCaretAtTheEnd.test b/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/quotedToNonQuotedCaretAtTheEnd.test new file mode 100644 index 00000000000..0a3c57ce475 --- /dev/null +++ b/idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/quotedToNonQuotedCaretAtTheEnd.test @@ -0,0 +1,5 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test.kt", + "newName": "foo" +} \ No newline at end of file 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 321ed0621db..8c25c97482f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java @@ -342,6 +342,12 @@ public class RenameTestGenerated extends AbstractRenameTest { doTest(fileName); } + @TestMetadata("nonQuotedToQuoted/nonQuotedToQuoted.test") + public void testNonQuotedToQuoted_NonQuotedToQuoted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/nonQuotedToQuoted/nonQuotedToQuoted.test"); + doTest(fileName); + } + @TestMetadata("noShadowingConflictForSiblingDeclarations/noShadowingConflictForSiblingDeclarations.test") public void testNoShadowingConflictForSiblingDeclarations_NoShadowingConflictForSiblingDeclarations() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/noShadowingConflictForSiblingDeclarations/noShadowingConflictForSiblingDeclarations.test"); @@ -438,6 +444,18 @@ public class RenameTestGenerated extends AbstractRenameTest { doTest(fileName); } + @TestMetadata("quotedToNonQuoted/quotedToNonQuoted.test") + public void testQuotedToNonQuoted_QuotedToNonQuoted() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/quotedToNonQuoted/quotedToNonQuoted.test"); + doTest(fileName); + } + + @TestMetadata("quotedToNonQuotedCaretAtTheEnd/quotedToNonQuotedCaretAtTheEnd.test") + public void testQuotedToNonQuotedCaretAtTheEnd_QuotedToNonQuotedCaretAtTheEnd() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/quotedToNonQuotedCaretAtTheEnd/quotedToNonQuotedCaretAtTheEnd.test"); + doTest(fileName); + } + @TestMetadata("renameArgumentsWhenParameterRenamed/parameter.test") public void testRenameArgumentsWhenParameterRenamed_Parameter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameArgumentsWhenParameterRenamed/parameter.test");