[kotlin] K2: add intention to convert receiver to context parameter

KTIJ-33555

GitOrigin-RevId: 0e2cda8c461a26177d5318f18d037c27591b41d3
This commit is contained in:
Pavel Kirpichenkov
2025-06-11 17:32:37 +00:00
committed by intellij-monorepo-bot
parent 9c4cfdbde4
commit 10d6fe208f
34 changed files with 368 additions and 6 deletions
@@ -2515,9 +2515,10 @@ hints.codevision.settings=Settings\u2026
convert.string.template.to.build.string=Convert to 'buildString' call
convert.concatenation.to.build.string=Convert concatenation to 'buildString' call
convert.to.indexed.function.call=Convert to indexed function call
convert.to.regular.parameter=Convert context parameter to value parameter
convert.to.receiver.parameter=Convert context parameter to receiver
convert.to.context.parameter=Convert value parameter to context parameter
convert.context.parameter.to.regular.parameter=Convert context parameter to value parameter
convert.context.parameter.to.receiver.parameter=Convert context parameter to receiver
convert.value.parameter.to.context.parameter=Convert value parameter to context parameter
convert.receiver.parameter.to.context.parameter=Convert receiver to context parameter
inspection.kotlin.invalid.bundle.or.property.display.name=Invalid property key
inspection.gradle.kotlinx.coroutines.deprecation.display.name=Incompatible kotlinx.coroutines dependency is used with Kotlin 1.3+ in Gradle
inspection.deprecated.gradle.dependency.display.name=Deprecated library is used in Gradle
@@ -0,0 +1,5 @@
<html>
<body>
Converts a function receiver to a context parameter and updates usages.
</body>
</html>
@@ -634,5 +634,12 @@
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ConvertReceiverParameterToContextParameterIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
</extensions>
</idea-plugin>
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
class ConvertContextParameterToReceiverIntention : SelfTargetingIntention<KtParameter>(
KtParameter::class.java, KotlinBundle.lazyMessage("convert.to.receiver.parameter")
KtParameter::class.java, KotlinBundle.lazyMessage("convert.context.parameter.to.receiver.parameter")
) {
override fun startInWriteAction(): Boolean = false
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinChangeInfo
import org.jetbrains.kotlin.psi.KtParameter
class ConvertContextParameterToRegularParameterIntention : SelfTargetingIntention<KtParameter>(
KtParameter::class.java, KotlinBundle.lazyMessage("convert.to.regular.parameter")
KtParameter::class.java, KotlinBundle.lazyMessage("convert.context.parameter.to.regular.parameter")
) {
override fun startInWriteAction(): Boolean = false
@@ -0,0 +1,64 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiDocumentManager
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention
import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinChangeInfo
import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinChangeSignatureProcessor
import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinMethodDescriptor
import org.jetbrains.kotlin.idea.refactoring.rename.KotlinMemberInplaceRenameHandler
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
class ConvertReceiverParameterToContextParameterIntention : SelfTargetingIntention<KtTypeReference>(
KtTypeReference::class.java,
KotlinBundle.lazyMessage("convert.receiver.parameter.to.context.parameter"),
), LowPriorityAction {
override fun startInWriteAction(): Boolean = false
override fun isApplicableTo(element: KtTypeReference, caretOffset: Int): Boolean {
return element.languageVersionSettings.supportsFeature(LanguageFeature.ContextParameters)
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
val ktFunction = element.getStrictParentOfType<KtNamedFunction>() ?: return
val methodDescriptor = KotlinMethodDescriptor(ktFunction)
val changeInfo = KotlinChangeInfo(methodDescriptor)
if (!configureChangeInfo(changeInfo)) return
object : KotlinChangeSignatureProcessor(element.project, changeInfo) {
override fun performRefactoring(usages: Array<out UsageInfo?>) {
super.performRefactoring(usages)
DumbService.getInstance(element.project).smartInvokeLater {
renameLastContextParameter(ktFunction, editor)
}
}
}.also {
it.prepareSuccessfulSwingThreadCallback = Runnable { }
}.run()
}
private fun configureChangeInfo(changeInfo: KotlinChangeInfo): Boolean {
val oldReceiverInfo = changeInfo.oldReceiverInfo ?: return false
changeInfo.receiverParameterInfo = null
oldReceiverInfo.isContextParameter = true
return true
}
private fun renameLastContextParameter(ktFunction: KtNamedFunction, editor: Editor?) {
if (!ktFunction.isValid || editor == null || editor.isDisposed) return
val lastContextParameter = ktFunction.contextReceiverList?.contextParameters()?.lastOrNull() ?: return
editor.caretModel.moveToOffset(lastContextParameter.startOffset)
PsiDocumentManager.getInstance(ktFunction.project).doPostponedOperationsAndUnblockDocument(editor.document)
KotlinMemberInplaceRenameHandler().doRename(lastContextParameter, editor, null)
}
}
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.psi.KtParameter
class ConvertRegularParameterToContextParameterIntention : SelfTargetingIntention<KtParameter>(
KtParameter::class.java,
KotlinBundle.lazyMessage("convert.to.context.parameter"),
KotlinBundle.lazyMessage("convert.value.parameter.to.context.parameter"),
) {
override fun startInWriteAction(): Boolean = false
@@ -14358,6 +14358,80 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest {
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter")
public static class ReceiverToContextParameter extends AbstractK2IntentionTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("classScopeExtensionArgument.kt")
public void testClassScopeExtensionArgument() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/classScopeExtensionArgument.kt");
}
@TestMetadata("contextFunCallInBody.kt")
public void testContextFunCallInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextFunCallInBody.kt");
}
@TestMetadata("contextMemberExtensionFunCallInBody.kt")
public void testContextMemberExtensionFunCallInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextMemberExtensionFunCallInBody.kt");
}
@TestMetadata("explicitExtensionArgument.kt")
public void testExplicitExtensionArgument() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/explicitExtensionArgument.kt");
}
@TestMetadata("explicitThisInBody.kt")
public void testExplicitThisInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/explicitThisInBody.kt");
}
@TestMetadata("extensionFunCallInBody.kt")
public void testExtensionFunCallInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/extensionFunCallInBody.kt");
}
@TestMetadata("implicitExtensionArgument.kt")
public void testImplicitExtensionArgument() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/implicitExtensionArgument.kt");
}
@TestMetadata("implicitThisInBody.kt")
public void testImplicitThisInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/implicitThisInBody.kt");
}
@TestMetadata("inapplicableDisabledFeature.kt")
public void testInapplicableDisabledFeature() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/inapplicableDisabledFeature.kt");
}
@TestMetadata("memberExtensionFunCallInBody.kt")
public void testMemberExtensionFunCallInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/memberExtensionFunCallInBody.kt");
}
@TestMetadata("memberFunCallInBody.kt")
public void testMemberFunCallInBody() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/memberFunCallInBody.kt");
}
@TestMetadata("withAnotherContextBefore.kt")
public void testWithAnotherContextBefore() throws Exception {
runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/withAnotherContextBefore.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/intentions/contextParameters/regularParameterToContextParameter")
public static class RegularParameterToContextParameter extends AbstractK2IntentionTest {
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ConvertReceiverParameterToContextParameterIntention
@@ -0,0 +1,14 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
interface Foo
context(c: Int)
fun <caret>Foo.foo(param: String) {
}
class Bar : Foo {
context(c1: Int)
fun baz() {
foo("boo")
}
}
@@ -0,0 +1,14 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
interface Foo
context(c: Int, foo: Foo)
fun foo(param: String) {
}
class Bar : Foo {
context(c1: Int)
fun baz() {
foo("boo")
}
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
fun <caret>String.foo() {
bar()
}
context(c: String)
fun bar() {
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(string: String) fun foo() {
bar()
}
context(c: String)
fun bar() {
}
@@ -0,0 +1,13 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
// K2_AFTER_ERROR: Unresolved reference 'bar'.
/* KTIJ-34522 */
context(c1: Int)
fun <caret>Bar.foo() {
"foo".bar()
}
class Bar {
context(c: Int)
fun String.bar() {}
}
@@ -0,0 +1,13 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
// K2_AFTER_ERROR: Unresolved reference 'bar'.
/* KTIJ-34522 */
context(c1: Int, bar: Bar)
fun foo() {
"foo".bar()
}
class Bar {
context(c: Int)
fun String.bar() {}
}
@@ -0,0 +1,10 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(c: Int)
fun <caret>String.foo(param: String) {
}
context(c1: Int)
fun bar() {
"baz".foo("boo")
}
@@ -0,0 +1,12 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(c: Int, string: String)
fun foo(param: String) {
}
context(c1: Int)
fun bar() {
with("baz") {
foo("boo")
}
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
fun <caret>String.foo() {
println(this)
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(string: String) fun foo() {
println(string)
}
@@ -0,0 +1,8 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
fun <caret>String.foo() {
bar()
}
fun String.bar() {
}
@@ -0,0 +1,8 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(string: String) fun foo() {
string.bar()
}
fun String.bar() {
}
@@ -0,0 +1,10 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(c: Int)
fun <caret>String.foo(param: String) {
}
context(c1: Int)
fun String.bar() {
foo("boo")
}
@@ -0,0 +1,10 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(c: Int, string: String)
fun foo(param: String) {
}
context(c1: Int)
fun String.bar() {
foo("boo")
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
fun <caret>String.foo() {
println(isEmpty())
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(string: String) fun foo() {
println(string.isEmpty())
}
@@ -0,0 +1,5 @@
// LANGUAGE_VERSION: 2.1
// IS_APPLICABLE: false
fun <caret>String.foo() {
}
@@ -0,0 +1,11 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
// K2_AFTER_ERROR: Unresolved reference 'bar'.
/* KTIJ-34522 */
fun <caret>Bar.foo() {
"foo".bar()
}
class Bar {
fun String.bar() {}
}
@@ -0,0 +1,11 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
// K2_AFTER_ERROR: Unresolved reference 'bar'.
/* KTIJ-34522 */
context(bar: Bar) fun foo() {
"foo".bar()
}
class Bar {
fun String.bar() {}
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
fun <caret>Bar.foo() {
bar()
}
class Bar {
fun bar() {}
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(bar: Bar) fun foo() {
bar.bar()
}
class Bar {
fun bar() {}
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(c1: Int)
fun <caret>String.foo() {
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(c1: Int, string: String)
fun foo() {
}