ReplaceWith: suggest for "invoke" extension
#KT-8597 Fixed
This commit is contained in:
committed by
Yan Zhulanow
parent
bb7d4c224f
commit
be194c3460
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.codeInliner
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
|
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.isInvokeOperator
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
@@ -34,7 +35,14 @@ class CallableUsageReplacementStrategy(
|
|||||||
if (!resolvedCall.status.isSuccess) return null
|
if (!resolvedCall.status.isSuccess) return null
|
||||||
|
|
||||||
val callElement = when (resolvedCall) {
|
val callElement = when (resolvedCall) {
|
||||||
is VariableAsFunctionResolvedCall -> resolvedCall.variableCall.call.callElement
|
is VariableAsFunctionResolvedCall -> {
|
||||||
|
val callElement = resolvedCall.variableCall.call.callElement
|
||||||
|
if (resolvedCall.resultingDescriptor.isInvokeOperator) {
|
||||||
|
callElement.parent as? KtCallExpression ?: callElement
|
||||||
|
} else {
|
||||||
|
callElement
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> resolvedCall.call.callElement
|
else -> resolvedCall.call.callElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.idea.core.*
|
|||||||
import org.jetbrains.kotlin.idea.inspections.RedundantUnitExpressionInspection
|
import org.jetbrains.kotlin.idea.inspections.RedundantUnitExpressionInspection
|
||||||
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
|
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
|
||||||
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention
|
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.isInvokeOperator
|
||||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||||
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.isError
|
import org.jetbrains.kotlin.types.isError
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
|
||||||
@@ -135,7 +137,23 @@ class CodeInliner<TCallElement : KtElement>(
|
|||||||
.forEach { ImportInsertHelper.getInstance(project).importDescriptor(file, it) }
|
.forEach { ImportInsertHelper.getInstance(project).importDescriptor(file, it) }
|
||||||
|
|
||||||
val replacementPerformer = when (elementToBeReplaced) {
|
val replacementPerformer = when (elementToBeReplaced) {
|
||||||
is KtExpression -> ExpressionReplacementPerformer(codeToInline, elementToBeReplaced)
|
is KtExpression -> {
|
||||||
|
if (descriptor.isInvokeOperator) {
|
||||||
|
val call = elementToBeReplaced as? KtCallExpression
|
||||||
|
?: (elementToBeReplaced as? KtDotQualifiedExpression)?.selectorExpression as? KtCallExpression
|
||||||
|
val callee = call?.calleeExpression
|
||||||
|
if (callee != null && callee.text != OperatorNameConventions.INVOKE.asString()) {
|
||||||
|
val receiverExpression = (codeToInline.mainExpression as? KtQualifiedExpression)?.receiverExpression
|
||||||
|
when {
|
||||||
|
elementToBeReplaced is KtCallExpression && receiverExpression is KtThisExpression ->
|
||||||
|
receiverExpression.replace(callee)
|
||||||
|
elementToBeReplaced is KtDotQualifiedExpression ->
|
||||||
|
receiverExpression?.replace(psiFactory.createExpressionByPattern("$0.$1", receiverExpression, callee))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExpressionReplacementPerformer(codeToInline, elementToBeReplaced)
|
||||||
|
}
|
||||||
is KtAnnotationEntry -> AnnotationEntryReplacementPerformer(codeToInline, elementToBeReplaced)
|
is KtAnnotationEntry -> AnnotationEntryReplacementPerformer(codeToInline, elementToBeReplaced)
|
||||||
is KtSuperTypeCallEntry -> SuperTypeCallEntryReplacementPerformer(codeToInline, elementToBeReplaced)
|
is KtSuperTypeCallEntry -> SuperTypeCallEntryReplacementPerformer(codeToInline, elementToBeReplaced)
|
||||||
else -> {
|
else -> {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.types.isFlexible
|
|||||||
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||||
import org.jetbrains.kotlin.util.OperatorChecks
|
import org.jetbrains.kotlin.util.OperatorChecks
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
fun KtContainerNode.description(): String? {
|
fun KtContainerNode.description(): String? {
|
||||||
when (node.elementType) {
|
when (node.elementType) {
|
||||||
@@ -364,3 +365,6 @@ fun KotlinType.reflectToRegularFunctionType(): KotlinType {
|
|||||||
if (isKSuspendFunctionType) builtIns.getSuspendFunction(parameterCount) else builtIns.getFunction(parameterCount)
|
if (isKSuspendFunctionType) builtIns.getSuspendFunction(parameterCount) else builtIns.getFunction(parameterCount)
|
||||||
return KotlinTypeFactory.simpleNotNullType(annotations, classDescriptor, arguments)
|
return KotlinTypeFactory.simpleNotNullType(annotations, classDescriptor, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val CallableDescriptor.isInvokeOperator: Boolean
|
||||||
|
get() = this is FunctionDescriptor && isOperator && name == OperatorNameConventions.INVOKE
|
||||||
|
|||||||
+6
-2
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
|||||||
import org.jetbrains.kotlin.idea.codeInliner.ClassUsageReplacementStrategy
|
import org.jetbrains.kotlin.idea.codeInliner.ClassUsageReplacementStrategy
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.UsageReplacementStrategy
|
import org.jetbrains.kotlin.idea.codeInliner.UsageReplacementStrategy
|
||||||
import org.jetbrains.kotlin.idea.core.OptionalParametersHelper
|
import org.jetbrains.kotlin.idea.core.OptionalParametersHelper
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.isInvokeOperator
|
||||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||||
@@ -193,7 +194,10 @@ abstract class DeprecatedSymbolUsageFixBase(
|
|||||||
): UsageReplacementStrategy? {
|
): UsageReplacementStrategy? {
|
||||||
val resolutionFacade = element.getResolutionFacade()
|
val resolutionFacade = element.getResolutionFacade()
|
||||||
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
||||||
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
|
val resolvedCall = element.getResolvedCall(bindingContext)
|
||||||
|
var target = resolvedCall?.resultingDescriptor?.takeIf { it.isInvokeOperator }
|
||||||
|
?: element.mainReference.resolveToDescriptors(bindingContext).singleOrNull()
|
||||||
|
?: return null
|
||||||
|
|
||||||
var replacePatternFromSymbol =
|
var replacePatternFromSymbol =
|
||||||
fetchReplaceWithPattern(target, resolutionFacade.project, element, replaceWith.replaceInWholeProject)
|
fetchReplaceWithPattern(target, resolutionFacade.project, element, replaceWith.replaceInWholeProject)
|
||||||
@@ -208,7 +212,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
|||||||
|
|
||||||
when (target) {
|
when (target) {
|
||||||
is CallableDescriptor -> {
|
is CallableDescriptor -> {
|
||||||
val resolvedCall = element.getResolvedCall(bindingContext) ?: return null
|
if (resolvedCall == null) return null
|
||||||
if (!resolvedCall.isReallySuccess()) return null
|
if (!resolvedCall.isReallySuccess()) return null
|
||||||
val replacement = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(
|
val replacement = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(
|
||||||
replaceWith, target, resolutionFacade, reformat
|
replaceWith, target, resolutionFacade, reformat
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// "Replace with 'execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
<caret>executor {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// "Replace with 'execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
executor.execute {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// "Replace with 'Foo.execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("Foo.execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Foo {
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
<caret>executor {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// "Replace with 'Foo.execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("Foo.execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Foo {
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
Foo.execute {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// "Replace with 'execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
<caret>invoke {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// "Replace with 'execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
execute {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// "Replace with 'execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
val self: Executor
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
executor.<caret>self {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// "Replace with 'execute(action)'" "true"
|
||||||
|
|
||||||
|
class Executor {
|
||||||
|
val self: Executor
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
@Deprecated("Use Executor.execute(Runnable) instead.", ReplaceWith("execute(action)"))
|
||||||
|
operator fun invoke(action: () -> Unit) {}
|
||||||
|
|
||||||
|
fun execute(action: () -> Unit) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usage(executor: Executor) {
|
||||||
|
executor.self.execute {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7061,6 +7061,26 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/in.kt");
|
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/in.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("invoke.kt")
|
||||||
|
public void testInvoke() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("invoke2.kt")
|
||||||
|
public void testInvoke2() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("invoke3.kt")
|
||||||
|
public void testInvoke3() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke3.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("invoke4.kt")
|
||||||
|
public void testInvoke4() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/invoke4.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("plusAssign.kt")
|
@TestMetadata("plusAssign.kt")
|
||||||
public void testPlusAssign() throws Exception {
|
public void testPlusAssign() throws Exception {
|
||||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/plusAssign.kt");
|
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/plusAssign.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user