Rename: Make improvements for "by-convention" calls

- Convert between conventions call for 'get' and 'invoke'
- Drop 'operator' when converting 'get'/'invoke' to something other than
  'invoke'/'get' respectively

 #KT-12365 Fixed
This commit is contained in:
Alexey Sedunov
2018-01-30 17:41:00 +03:00
parent caeb594e09
commit f35af11423
16 changed files with 163 additions and 11 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.psi;
import com.google.common.collect.ImmutableSet;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.LocalSearchScope;
@@ -32,6 +33,9 @@ import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import org.jetbrains.kotlin.psi.stubs.KotlinStubWithFqName;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.Set;
import static org.jetbrains.kotlin.psi.KtPsiFactoryKt.KtPsiFactory;
@@ -78,16 +82,26 @@ abstract class KtNamedDeclarationStub<T extends KotlinStubWithFqName<?>> extends
return findChildByType(KtTokens.IDENTIFIER);
}
private static final Set<String> FUNCTIONLIKE_CONVENTIONS = ImmutableSet.of(
OperatorNameConventions.INVOKE.asString(),
OperatorNameConventions.GET.asString()
);
private static boolean shouldDropOperatorKeyword(String oldName, String newName) {
return !OperatorConventions.isConventionName(Name.identifier(newName)) ||
FUNCTIONLIKE_CONVENTIONS.contains(oldName) != FUNCTIONLIKE_CONVENTIONS.contains(newName);
}
@Override
public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
PsiElement identifier = getNameIdentifier();
if (identifier == null) return null;
KtModifierList modifierList = getModifierList();
if (modifierList != null &&
modifierList.hasModifier(KtTokens.OPERATOR_KEYWORD) &&
!OperatorConventions.isConventionName(Name.identifier(name))) {
removeModifier(KtTokens.OPERATOR_KEYWORD);
if (modifierList != null && modifierList.hasModifier(KtTokens.OPERATOR_KEYWORD)) {
if (shouldDropOperatorKeyword(getName(), name)) {
removeModifier(KtTokens.OPERATOR_KEYWORD);
}
}
PsiElement newIdentifier = KtPsiFactory(this).createNameIdentifierIfPossible(name);
@@ -27,8 +27,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtArrayAccessExpression;
import org.jetbrains.kotlin.psi.KtContainerNode;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.util.OperatorNameConventions;
@@ -98,6 +97,22 @@ public class KtArrayAccessReference extends KtSimpleReference<KtArrayAccessExpre
@Nullable
@Override
public PsiElement handleElementRename(@Nullable String newElementName) {
KtArrayAccessExpression arrayAccessExpression = getExpression();
if (OperatorNameConventions.INVOKE.asString().equals(newElementName)) {
KtExpression callExpression = CreateByPatternKt.buildExpression(
new KtPsiFactory(arrayAccessExpression.getProject()),
true,
pattern -> {
pattern.appendExpression(arrayAccessExpression.getArrayExpression());
pattern.appendFixedText("(");
pattern.appendExpressions(arrayAccessExpression.getIndexExpressions(), ",");
pattern.appendFixedText(")");
return null;
}
);
return arrayAccessExpression.replace(callExpression);
}
return ReferenceUtilKt.renameImplicitConventionalCall(this, newElementName);
}
@@ -23,9 +23,7 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.unpackFunctionLiteral
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -95,7 +93,20 @@ class KtInvokeFunctionReference(expression: KtCallExpression) : KtSimpleReferenc
override fun canRename(): Boolean = true
override fun handleElementRename(newElementName: String?): PsiElement? = renameImplicitConventionalCall(newElementName)
override fun handleElementRename(newElementName: String?): PsiElement? {
val callExpression = expression
if (newElementName == OperatorNameConventions.GET.asString() && callExpression.typeArguments.isEmpty()) {
val arrayAccessExpression = KtPsiFactory(callExpression).buildExpression {
appendExpression(callExpression.calleeExpression)
appendFixedText("[")
appendExpressions(callExpression.valueArguments.map { it.getArgumentExpression() })
appendFixedText("]")
}
return callExpression.replace(arrayAccessExpression)
}
return renameImplicitConventionalCall(newElementName)
}
companion object {
+7
View File
@@ -0,0 +1,7 @@
class A {
operator fun <caret>get(n: Int, s: String) = 1
}
fun test() {
A()[1, "2"]
}
@@ -0,0 +1,7 @@
class A {
fun foo(n: Int, s: String) = 1
}
fun test() {
A().foo(1, "2")
}
@@ -0,0 +1,7 @@
class A {
operator fun <caret>get(n: Int, s: String) = 1
}
fun test() {
A()[1, "2"]
}
@@ -0,0 +1,7 @@
class A {
operator fun invoke(n: Int, s: String) = 1
}
fun test() {
A()(1, "2")
}
+7
View File
@@ -0,0 +1,7 @@
class A {
operator fun <caret>get(n: Int, s: String) = 1
}
fun test() {
A()[1, "2"]
}
@@ -0,0 +1,7 @@
class A {
fun plus(n: Int, s: String) = 1
}
fun test() {
A().plus(1, "2")
}
@@ -0,0 +1,7 @@
class A {
operator fun <caret>invoke(n: Int, s: String) = 1
}
fun test() {
A()(1, "2")
}
@@ -0,0 +1,7 @@
class A {
fun foo(n: Int, s: String) = 1
}
fun test() {
A().foo(1, "2")
}
@@ -0,0 +1,7 @@
class A {
operator fun <caret>invoke(n: Int, s: String) = 1
}
fun test() {
A()(1, "2")
}
@@ -0,0 +1,7 @@
class A {
operator fun get(n: Int, s: String) = 1
}
fun test() {
A()[1, "2"]
}
@@ -0,0 +1,7 @@
class A {
operator fun <caret>invoke(n: Int, s: String) = 1
}
fun test() {
A()(1, "2")
}
@@ -0,0 +1,7 @@
class A {
fun plus(n: Int, s: String) = 1
}
fun test() {
A().plus(1, "2")
}
@@ -83,7 +83,31 @@ class InplaceRenameTest : LightPlatformCodeInsightTestCase() {
}
fun testNoReformat() {
doTestInplaceRename("subject2", MemberInplaceRenameHandler())
doTestMemberInplaceRename("subject2")
}
fun testInvokeToFoo() {
doTestMemberInplaceRename("foo")
}
fun testInvokeToGet() {
doTestMemberInplaceRename("get")
}
fun testInvokeToPlus() {
doTestMemberInplaceRename("plus")
}
fun testGetToFoo() {
doTestMemberInplaceRename("foo")
}
fun testGetToInvoke() {
doTestMemberInplaceRename("invoke")
}
fun testGetToPlus() {
doTestMemberInplaceRename("plus")
}
private fun doTestImplicitLambdaParameter(newName: String) {
@@ -136,6 +160,10 @@ class InplaceRenameTest : LightPlatformCodeInsightTestCase() {
checkResultByFile(getTestName(false) + ".kt.after")
}
private fun doTestMemberInplaceRename(newName: String?) {
doTestInplaceRename(newName, MemberInplaceRenameHandler())
}
private fun doTestInplaceRename(newName: String?, handler: VariableInplaceRenameHandler = VariableInplaceRenameHandler()) {
configureByFile(getTestName(false) + ".kt")
val element = TargetElementUtil.findTargetElement(