[kotlin] Port ReplaceExplicitFunctionLiteralParamWithItIntention for K2

^KTIJ-30542

GitOrigin-RevId: 04c20b21e48b497a15f1a5b71696554005cb2ffd
This commit is contained in:
Andrey Cherkasov
2024-07-10 18:54:45 +00:00
committed by intellij-monorepo-bot
parent 8964bcba12
commit 47a4785a87
12 changed files with 313 additions and 3 deletions
@@ -351,5 +351,12 @@
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ReplaceExplicitFunctionLiteralParamWithItIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
</extensions>
</idea-plugin>
@@ -0,0 +1,104 @@
// Copyright 2000-2024 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.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.impl.Utils.computeWithProgressIcon
import com.intellij.openapi.application.readAction
import com.intellij.openapi.editor.Editor
import com.intellij.ui.awt.RelativePoint
import com.intellij.util.application
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.resolution.singleFunctionCallOrNull
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.analysis.api.symbols.KaAnonymousFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention
import org.jetbrains.kotlin.idea.codeinsight.utils.findExistingEditor
import org.jetbrains.kotlin.idea.codeinsights.impl.base.intentions.ReplaceExplicitLambdaParameterWithItUtils.ParamRenamingProcessor
import org.jetbrains.kotlin.idea.codeinsights.impl.base.intentions.ReplaceExplicitLambdaParameterWithItUtils.createAnalyzableExpression
import org.jetbrains.kotlin.idea.codeinsights.impl.base.intentions.ReplaceExplicitLambdaParameterWithItUtils.getLambda
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFunctionLiteral
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
internal class ReplaceExplicitFunctionLiteralParamWithItIntention : SelfTargetingIntention<KtElement>(
KtElement::class.java,
KotlinBundle.lazyMessage("replace.explicit.lambda.parameter.with.it"),
) {
override fun startInWriteAction(): Boolean = false
override fun isApplicableTo(element: KtElement, caretOffset: Int): Boolean {
val functionLiteral = targetFunctionLiteral(element, caretOffset) ?: return false
val explicitParameterName = functionLiteral.valueParameters.singleOrNull()?.name ?: return false
val lambda = getLambda(element, functionLiteral) ?: return false
val call = lambda.getStrictParentOfType<KtCallExpression>()
if (call != null) {
val argumentIndex = call.valueArguments.indexOfFirst { it.getArgumentExpression() == lambda }
val callOrQualified = call.getQualifiedExpressionForSelectorOrThis()
val newCallOrQualified = createAnalyzableExpression(element.project, argumentIndex, callOrQualified) ?: return false
val codeFragment = KtPsiFactory(element.project).createExpressionCodeFragment(newCallOrQualified.text, element)
val contentElement = codeFragment.getContentElement()!!
return computeWithProgressIconIfNeeded(element.findExistingEditor()!!, caretOffset) {
analyze(contentElement) {
val resolveToCall = contentElement.getPossiblyQualifiedCallExpression()?.resolveToCall()
resolveToCall?.singleFunctionCallOrNull()?.partiallyAppliedSymbol?.symbol != null
}
}
}
setTextGetter { KotlinBundle.message("replace.explicit.parameter.0.with.it", explicitParameterName) }
return true
}
override fun applyTo(element: KtElement, editor: Editor?) {
val caretOffset = editor?.caretModel?.offset ?: return
val functionLiteral = targetFunctionLiteral(element, editor.caretModel.offset) ?: return
val cursorInParameterList = functionLiteral.valueParameterList?.textRange?.containsOffset(caretOffset) ?: return
ParamRenamingProcessor(editor, functionLiteral, cursorInParameterList).run()
}
}
private fun targetFunctionLiteral(element: KtElement, caretOffset: Int): KtFunctionLiteral? {
val expression = element.getParentOfType<KtNameReferenceExpression>(false)
if (expression != null) {
return computeWithProgressIconIfNeeded(element.findExistingEditor()!!, caretOffset) {
analyze(expression) {
val target = expression.mainReference.resolveToSymbols().singleOrNull() as? KaValueParameterSymbol ?: return@analyze null
val functionDescriptor = target.containingSymbol as? KaAnonymousFunctionSymbol ?: return@analyze null
functionDescriptor.psi as? KtFunctionLiteral
}
}
}
val functionLiteral = element.getParentOfType<KtFunctionLiteral>(true) ?: return null
val arrow = functionLiteral.arrow ?: return null
if (caretOffset > arrow.endOffset) return null
return functionLiteral
}
private fun <T> computeWithProgressIconIfNeeded(editor: Editor, caretOffset: Int, action: () -> T): T {
return if (application.isDispatchThread()) {
val aComponent = editor.contentComponent
val point = RelativePoint(aComponent, editor.logicalPositionToXY(editor.offsetToLogicalPosition(caretOffset)))
computeWithProgressIcon(point, aComponent, ActionPlaces.UNKNOWN) {
readAction { action() }
}
}
else {
action()
}
}
@@ -7076,7 +7076,149 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest {
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt")
public static class ReplaceExplicitFunctionLiteralParamWithIt 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("applicable_InIf.kt")
public void testApplicable_InIf() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_InIf.kt");
}
@TestMetadata("applicable_InIfElse.kt")
public void testApplicable_InIfElse() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_InIfElse.kt");
}
@TestMetadata("applicable_cursofOverParamInInnerLiteral.kt")
public void testApplicable_cursofOverParamInInnerLiteral() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_cursofOverParamInInnerLiteral.kt");
}
@TestMetadata("applicable_cursorOverParameterDeclaration.kt")
public void testApplicable_cursorOverParameterDeclaration() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_cursorOverParameterDeclaration.kt");
}
@TestMetadata("applicable_cursorOverParameterUse.kt")
public void testApplicable_cursorOverParameterUse() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_cursorOverParameterUse.kt");
}
@TestMetadata("applicable_formatsProperly.kt")
public void testApplicable_formatsProperly() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_formatsProperly.kt");
}
@TestMetadata("applicable_inPropertyInitializer.kt")
public void testApplicable_inPropertyInitializer() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_inPropertyInitializer.kt");
}
@TestMetadata("applicable_inWhenEntry.kt")
public void testApplicable_inWhenEntry() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_inWhenEntry.kt");
}
@TestMetadata("applicable_nestedLiteralsNoUseInside.kt")
public void testApplicable_nestedLiteralsNoUseInside() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_nestedLiteralsNoUseInside.kt");
}
@TestMetadata("applicable_overloadResolutionUnambiguity.kt")
public void testApplicable_overloadResolutionUnambiguity() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_overloadResolutionUnambiguity.kt");
}
@TestMetadata("applicable_overloadResolutionUnambiguity2.kt")
public void testApplicable_overloadResolutionUnambiguity2() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_overloadResolutionUnambiguity2.kt");
}
@TestMetadata("applicable_qualifiedExpression.kt")
public void testApplicable_qualifiedExpression() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_qualifiedExpression.kt");
}
@TestMetadata("notApplicable_InIf.kt")
public void testNotApplicable_InIf() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_InIf.kt");
}
@TestMetadata("notApplicable_InIfElse.kt")
public void testNotApplicable_InIfElse() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_InIfElse.kt");
}
@TestMetadata("notApplicable_alreadyUsesImplicitIt.kt")
public void testNotApplicable_alreadyUsesImplicitIt() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_alreadyUsesImplicitIt.kt");
}
@TestMetadata("notApplicable_hasDestructuringDeclaration.kt")
public void testNotApplicable_hasDestructuringDeclaration() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_hasDestructuringDeclaration.kt");
}
@TestMetadata("notApplicable_hasMultipleParameters.kt")
public void testNotApplicable_hasMultipleParameters() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_hasMultipleParameters.kt");
}
@TestMetadata("notApplicable_inWhenEntry.kt")
public void testNotApplicable_inWhenEntry() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_inWhenEntry.kt");
}
@TestMetadata("notApplicable_itFromOuterLambda.kt")
public void testNotApplicable_itFromOuterLambda() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_itFromOuterLambda.kt");
}
@TestMetadata("notApplicable_nestedLiterals.kt")
public void testNotApplicable_nestedLiterals() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_nestedLiterals.kt");
}
@TestMetadata("notApplicable_nestedLiteralsNoIt.kt")
public void testNotApplicable_nestedLiteralsNoIt() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_nestedLiteralsNoIt.kt");
}
@TestMetadata("notApplicable_notFunctionLiteralParameter.kt")
public void testNotApplicable_notFunctionLiteralParameter() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_notFunctionLiteralParameter.kt");
}
@TestMetadata("notApplicable_overloadResolutionAmbiguity.kt")
public void testNotApplicable_overloadResolutionAmbiguity() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_overloadResolutionAmbiguity.kt");
}
@TestMetadata("notApplicable_overloadResolutionAmbiguity2.kt")
public void testNotApplicable_overloadResolutionAmbiguity2() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_overloadResolutionAmbiguity2.kt");
}
@TestMetadata("notApplicable_parameterHasExplicitType.kt")
public void testNotApplicable_parameterHasExplicitType() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_parameterHasExplicitType.kt");
}
@TestMetadata("notApplicable_wrongPosition.kt")
public void testNotApplicable_wrongPosition() throws Exception {
runTest("../../../idea/tests/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_wrongPosition.kt");
}
}
@@ -17200,6 +17200,16 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest {
runTest("testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_nestedLiteralsNoUseInside.kt");
}
@TestMetadata("applicable_overloadResolutionUnambiguity.kt")
public void testApplicable_overloadResolutionUnambiguity() throws Exception {
runTest("testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_overloadResolutionUnambiguity.kt");
}
@TestMetadata("applicable_overloadResolutionUnambiguity2.kt")
public void testApplicable_overloadResolutionUnambiguity2() throws Exception {
runTest("testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_overloadResolutionUnambiguity2.kt");
}
@TestMetadata("applicable_qualifiedExpression.kt")
public void testApplicable_qualifiedExpression() throws Exception {
runTest("testData/intentions/replaceExplicitFunctionLiteralParamWithIt/applicable_qualifiedExpression.kt");
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ReplaceExplicitFunctionLiteralParamWithItIntention
@@ -0,0 +1,9 @@
fun test() {
foo { <caret>i -> i + 1 }
}
fun foo(f: (Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
// IGNORE_K1
// the intention is inapplicable for K1, see the paired testNotApplicable_overloadResolutionAmbiguity
@@ -0,0 +1,9 @@
fun test() {
foo { it + 1 }
}
fun foo(f: (Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
// IGNORE_K1
// the intention is inapplicable for K1, see the paired testNotApplicable_overloadResolutionAmbiguity
@@ -0,0 +1,11 @@
fun test() {
C().foo { <caret>i -> i + 1 }
}
class C {
fun foo(f: (Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
}
// IGNORE_K1
// the intention is inapplicable for K1, see the paired testNotApplicable_overloadResolutionAmbiguity2
@@ -0,0 +1,11 @@
fun test() {
C().foo { it + 1 }
}
class C {
fun foo(f: (Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
}
// IGNORE_K1
// the intention is inapplicable for K1, see the paired testNotApplicable_overloadResolutionAmbiguity2
@@ -4,4 +4,7 @@ fun test() {
}
fun foo(f: (Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
// IGNORE_K2
// the intention is applicable for K2, see the paired applicable_overloadResolutionUnambiguity
@@ -6,4 +6,7 @@ fun test() {
class C {
fun foo(f: (Int) -> Int) {}
fun foo(f: (Int, Int) -> Int) {}
}
}
// IGNORE_K2
// the intention is applicable for K2, see the paired applicable_overloadResolutionUnambiguity2
@@ -79,7 +79,7 @@ internal fun MutableTWorkspace.generateK2IntentionTests() {
model("${idea}intentions/replaceUntilWithRangeTo", pattern = pattern, isIgnored = true)
model("${idea}intentions/convertLateinitPropertyToNullable", pattern = pattern, isIgnored = true)
model("${idea}intentions/swapStringEqualsIgnoreCase", pattern = pattern, isIgnored = true)
model("${idea}intentions/replaceExplicitFunctionLiteralParamWithIt", pattern = pattern, isIgnored = true)
model("${idea}intentions/replaceExplicitFunctionLiteralParamWithIt", pattern = pattern)
model("${idea}intentions/nullableBooleanEqualityCheckToElvis", pattern = pattern, isIgnored = true)
model("${idea}intentions/convertOrdinaryPropertyToLazy", pattern = pattern, isIgnored = true)
model("${idea}intentions/replaceWithOrdinaryAssignment", pattern = pattern, isIgnored = true)