Fix Can't rename implicit lambda parameter 'it' when caret is placed right after the last character

#KT-14401 Fixed
This commit is contained in:
Kirill Rakhman
2017-03-02 23:16:00 +03:00
committed by Alexey Sedunov
parent e3391175d9
commit 1bad04db50
5 changed files with 63 additions and 43 deletions
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2017 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,17 +21,15 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler
import org.jetbrains.kotlin.idea.intentions.ReplaceItWithExplicitFunctionLiteralParamIntention import org.jetbrains.kotlin.idea.intentions.ReplaceItWithExplicitFunctionLiteralParamIntention
import org.jetbrains.kotlin.idea.intentions.isAutoCreatedItUsage import org.jetbrains.kotlin.idea.intentions.isAutoCreatedItUsage
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.KtNameReferenceExpression
class RenameKotlinImplicitLambdaParameter: VariableInplaceRenameHandler() { class RenameKotlinImplicitLambdaParameter : VariableInplaceRenameHandler() {
override fun isAvailable(element: PsiElement?, editor: Editor, file: PsiFile): Boolean { override fun isAvailable(element: PsiElement?, editor: Editor, file: PsiFile): Boolean {
val nameExpression = PsiTreeUtil.findElementOfClassAtOffset( val nameExpression = file.findElementForRename<KtNameReferenceExpression>(editor.caretModel.offset)
file, editor.caretModel.offset, KtNameReferenceExpression::class.java, false)
return nameExpression != null && isAutoCreatedItUsage(nameExpression) return nameExpression != null && isAutoCreatedItUsage(nameExpression)
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2017 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
package org.jetbrains.kotlin.idea.refactoring.rename package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.rename.ResolvableCollisionUsageInfo import com.intellij.refactoring.rename.ResolvableCollisionUsageInfo
import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo
import com.intellij.refactoring.util.MoveRenameUsageInfo import com.intellij.refactoring.util.MoveRenameUsageInfo
@@ -79,4 +81,9 @@ class LostDefaultValuesInOverridingFunctionUsageInfo(
subParam.addRange(superParam.equalsToken, defaultValue) subParam.addRange(superParam.equalsToken, defaultValue)
} }
} }
}
inline fun <reified T : PsiElement> PsiFile.findElementForRename(offset: Int): T? {
return PsiTreeUtil.findElementOfClassAtOffset(this, offset, T::class.java, false)
?: PsiTreeUtil.findElementOfClassAtOffset(this, (offset - 1).coerceAtLeast(0), T::class.java, false)
} }
@@ -0,0 +1,3 @@
fun f() {
val f: (Int) -> Int = { it<caret> + it }
}
@@ -0,0 +1,3 @@
fun f() {
val f: (Int) -> Int = { y -> y + y }
}
@@ -28,6 +28,8 @@ import org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinImplicitLambdaPa
import com.intellij.codeInsight.template.impl.TemplateManagerImpl import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.TemplateManager import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import org.jetbrains.kotlin.idea.refactoring.rename.findElementForRename
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
class InplaceRenameTest : LightPlatformCodeInsightTestCase() { class InplaceRenameTest : LightPlatformCodeInsightTestCase() {
override fun isRunInWriteAction(): Boolean = false override fun isRunInWriteAction(): Boolean = false
@@ -50,19 +52,58 @@ class InplaceRenameTest : LightPlatformCodeInsightTestCase() {
} }
fun testFunctionLiteralIt() { fun testFunctionLiteralIt() {
doTestImplicitLambdaParameter("y")
}
fun testFunctionLiteralItEndCaret() {
doTestImplicitLambdaParameter("y")
}
fun testFunctionLiteralParenthesis() {
doTestInplaceRename("y")
}
fun testLocalFunction() {
doTestInplaceRename("bar")
}
fun testFunctionParameterNotInplace() {
doTestInplaceRename(null)
}
fun testGlobalFunctionNotInplace() {
doTestInplaceRename(null)
}
fun testTopLevelValNotInplace() {
doTestInplaceRename(null)
}
fun testLabelFromFunction() {
doTestInplaceRename("foo")
}
fun testMultiDeclaration() {
doTestInplaceRename("foo")
}
fun testLocalVarShadowingMemberProperty() {
doTestInplaceRename("name1")
}
private fun doTestImplicitLambdaParameter(newName: String) {
configureByFile(getTestName(false) + ".kt") configureByFile(getTestName(false) + ".kt")
val newName = "y"
// This code is copy-pasted from CodeInsightTestUtil.doInlineRename() and slightly modified. // This code is copy-pasted from CodeInsightTestUtil.doInlineRename() and slightly modified.
// Original method was not suitable because it expects renamed element to be reference to other or referrable // Original method was not suitable because it expects renamed element to be reference to other or referrable
val file = LightPlatformCodeInsightTestCase.getFile()!! val file = getFile()!!
val editor = LightPlatformCodeInsightTestCase.getEditor()!! val editor = getEditor()!!
val element = file.findReferenceAt(editor.caretModel.offset)!!.element val element = file.findElementForRename<KtNameReferenceExpression>(editor.caretModel.offset)!!
assertNotNull(element) assertNotNull(element)
val dataContext = SimpleDataContext.getSimpleContext(CommonDataKeys.PSI_ELEMENT.name, element!!, val dataContext = SimpleDataContext.getSimpleContext(CommonDataKeys.PSI_ELEMENT.name, element!!,
LightPlatformCodeInsightTestCase.getCurrentEditorDataContext()) getCurrentEditorDataContext())
val handler = RenameKotlinImplicitLambdaParameter() val handler = RenameKotlinImplicitLambdaParameter()
assertTrue(handler.isRenaming(dataContext), "In-place rename not allowed for " + element) assertTrue(handler.isRenaming(dataContext), "In-place rename not allowed for " + element)
@@ -100,38 +141,6 @@ class InplaceRenameTest : LightPlatformCodeInsightTestCase() {
checkResultByFile(getTestName(false) + ".kt.after") checkResultByFile(getTestName(false) + ".kt.after")
} }
fun testFunctionLiteralParenthesis() {
doTestInplaceRename("y")
}
fun testLocalFunction() {
doTestInplaceRename("bar")
}
fun testFunctionParameterNotInplace() {
doTestInplaceRename(null)
}
fun testGlobalFunctionNotInplace() {
doTestInplaceRename(null)
}
fun testTopLevelValNotInplace() {
doTestInplaceRename(null)
}
fun testLabelFromFunction() {
doTestInplaceRename("foo")
}
fun testMultiDeclaration() {
doTestInplaceRename("foo")
}
fun testLocalVarShadowingMemberProperty() {
doTestInplaceRename("name1")
}
private fun doTestInplaceRename(newName: String?) { private fun doTestInplaceRename(newName: String?) {
configureByFile(getTestName(false) + ".kt") configureByFile(getTestName(false) + ".kt")
val element = TargetElementUtilBase.findTargetElement( val element = TargetElementUtilBase.findTargetElement(