[kotlin] k2: port SpecifyExplicitLambdaSignatureIntention

- parameter names in functional types are not supported yet (KT-65846)

^KTIJ-27431 fixed

GitOrigin-RevId: efd9a1fdcafb98db5b43600acb47f1f19e7c9153
This commit is contained in:
Anna Kozlova
2024-02-16 11:40:35 +00:00
committed by intellij-monorepo-bot
parent 1b565a13b9
commit f6fe490e42
8 changed files with 237 additions and 3 deletions
@@ -30,6 +30,13 @@
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.SpecifyExplicitLambdaSignatureIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.AddPropertyAccessorsIntention</className>
@@ -0,0 +1,59 @@
// 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.codeInspection.util.IntentionFamilyName
import com.intellij.codeInspection.util.IntentionName
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtAnonymousFunctionSymbol
import org.jetbrains.kotlin.analysis.api.types.KtErrorType
import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType
import org.jetbrains.kotlin.idea.base.psi.shouldLambdaParameterBeNamed
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AbstractKotlinModCommandWithContext
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AnalysisActionContext
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.KotlinApplicabilityRange
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.applicabilityRange
import org.jetbrains.kotlin.idea.codeinsight.utils.NamedArgumentUtils
import org.jetbrains.kotlin.idea.codeinsights.impl.base.applicators.ApplicabilityRanges
import org.jetbrains.kotlin.idea.k2.refactoring.util.LambdaToAnonymousFunctionUtil
import org.jetbrains.kotlin.idea.k2.refactoring.util.getExplicitLambdaSignature
import org.jetbrains.kotlin.idea.k2.refactoring.util.specifyExplicitLambdaSignature
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtCallElement
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtLambdaArgument
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
internal class SpecifyExplicitLambdaSignatureIntention: AbstractKotlinModCommandWithContext<KtLambdaExpression, String>(
KtLambdaExpression::class
) {
override fun getFamilyName(): @IntentionFamilyName String = KotlinBundle.message("specify.explicit.lambda.signature")
override fun getActionName(element: KtLambdaExpression, context: String): @IntentionName String = familyName
context(KtAnalysisSession)
override fun prepareContext(element: KtLambdaExpression): String? {
return getExplicitLambdaSignature(element)
}
override fun getApplicabilityRange(): KotlinApplicabilityRange<KtLambdaExpression> {
return ApplicabilityRanges.SELF
}
override fun isApplicableByPsi(element: KtLambdaExpression): Boolean {
return element.functionLiteral.arrow == null || !element.valueParameters.all { it.typeReference != null }
}
override fun apply(
element: KtLambdaExpression,
context: AnalysisActionContext<String>,
updater: ModPsiUpdater
) {
specifyExplicitLambdaSignature(element, context.analyzeContext)
}
}
@@ -5351,6 +5351,109 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest {
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature")
public static class SpecifyExplicitLambdaSignature extends AbstractK2IntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("anonymous.kt")
public void testAnonymous() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/anonymous.kt");
}
@TestMetadata("coercionToUnit.kt")
public void testCoercionToUnit() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/coercionToUnit.kt");
}
@TestMetadata("cursorOnRightBrace.kt")
public void testCursorOnRightBrace() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/cursorOnRightBrace.kt");
}
@TestMetadata("definitelyNonNullTypeMultipleParam.kt")
public void testDefinitelyNonNullTypeMultipleParam() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/definitelyNonNullTypeMultipleParam.kt");
}
@TestMetadata("definitelyNonNullTypeSingleParam.kt")
public void testDefinitelyNonNullTypeSingleParam() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/definitelyNonNullTypeSingleParam.kt");
}
@TestMetadata("delegate.kt")
public void testDelegate() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/delegate.kt");
}
@TestMetadata("destructuring.kt")
public void testDestructuring() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/destructuring.kt");
}
@TestMetadata("emptyParamListWithBrackets.kt")
public void testEmptyParamListWithBrackets() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithBrackets.kt");
}
@TestMetadata("emptyParamListWithIt.kt")
public void testEmptyParamListWithIt() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithIt.kt");
}
@TestMetadata("emptyParamListWithWhiteSpace.kt")
public void testEmptyParamListWithWhiteSpace() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithWhiteSpace.kt");
}
@TestMetadata("emptyParamListWithoutItWithArrow.kt")
public void testEmptyParamListWithoutItWithArrow() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithoutItWithArrow.kt");
}
@TestMetadata("lambdaWithLambdaAsParam.kt")
public void testLambdaWithLambdaAsParam() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt");
}
@TestMetadata("manyNewlines.kt")
public void testManyNewlines() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/manyNewlines.kt");
}
@TestMetadata("multipleParam.kt")
public void testMultipleParam() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/multipleParam.kt");
}
@TestMetadata("noParameters.kt")
public void testNoParameters() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/noParameters.kt");
}
@TestMetadata("shortenReferencesForParams.kt")
public void testShortenReferencesForParams() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/shortenReferencesForParams.kt");
}
@TestMetadata("singleParam.kt")
public void testSingleParam() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/singleParam.kt");
}
@TestMetadata("typesAlreadyExplicit.kt")
public void testTypesAlreadyExplicit() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/typesAlreadyExplicit.kt");
}
@TestMetadata("typesNotInferable.kt")
public void testTypesNotInferable() throws Exception {
runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/typesNotInferable.kt");
}
}
@@ -5616,8 +5719,6 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest {
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.k2.codeinsight.intentions.SpecifyExplicitLambdaSignatureIntention
@@ -9,3 +9,5 @@ public class TestingUse {
fun main() {
val funcInfunc = TestingUse().test6({<caret>f -> f(5) > 20}, {x -> x + 2})
}
// IGNORE_K2
@@ -9,3 +9,5 @@ public class TestingUse {
fun main() {
val funcInfunc = TestingUse().test6({ f: (x: Int) -> Int -> f(5) > 20}, { x -> x + 2})
}
// IGNORE_K2
@@ -0,0 +1,62 @@
// 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.refactoring.util
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.types.KtErrorType
import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences
import org.jetbrains.kotlin.psi.KtFunctionLiteral
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtParameterList
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.types.Variance
context(KtAnalysisSession)
fun getExplicitLambdaSignature(element: KtLambdaExpression): String? {
val lambdaSymbol = element.functionLiteral.getSymbol() as KtFunctionLikeSymbol
val valueParameters = lambdaSymbol.valueParameters
if (valueParameters.any { it.returnType is KtErrorType } ) return null
return valueParameters.joinToString { param ->
val parameter = param.psi as? KtParameter
if (parameter != null) {
(parameter.name ?: parameter.destructuringDeclaration?.text
?: "_") + ": " + param.returnType.render(position = Variance.IN_VARIANCE)
} else param.render()
}
}
fun specifyExplicitLambdaSignature(element: KtLambdaExpression) {
val parameterString = analyze(element) { getExplicitLambdaSignature(element) } ?: return
specifyExplicitLambdaSignature(element, parameterString)
}
fun specifyExplicitLambdaSignature(element: KtLambdaExpression, parameterString: String) {
val psiFactory = KtPsiFactory(element.project)
val functionLiteral = element.functionLiteral
val newParameterList = (psiFactory.createExpression("{ $parameterString -> }") as KtLambdaExpression).functionLiteral.valueParameterList
fun KtFunctionLiteral.setParameterListIfAny(psiFactory: KtPsiFactory, newParameterList: KtParameterList?) {
val oldParameterList = valueParameterList
if (oldParameterList != null && newParameterList != null) {
oldParameterList.replace(newParameterList)
} else {
val openBraceElement = lBrace
val nextSibling = openBraceElement.nextSibling
val addNewline = nextSibling is PsiWhiteSpace && nextSibling.text?.contains("\n") ?: false
val (whitespace, arrow) = psiFactory.createWhitespaceAndArrow()
addRangeAfter(whitespace, arrow, openBraceElement)
if (newParameterList != null) {
addAfter(newParameterList, openBraceElement)
}
if (addNewline) {
addAfter(psiFactory.createNewLine(), openBraceElement)
}
}
}
functionLiteral.setParameterListIfAny(psiFactory, newParameterList)
shortenReferences(element.valueParameters)
}
@@ -51,6 +51,7 @@ internal fun MutableTWorkspace.generateK2IntentionTests() {
model("${idea}intentions/invertIfCondition", pattern = pattern)
model("${idea}intentions/lambdaToAnonymousFunction", pattern = pattern)
model("${idea}intentions/removeExplicitTypeArguments", pattern = pattern)
model("${idea}intentions/specifyExplicitLambdaSignature", pattern = pattern)
//
model("${idea}intentions/addJvmOverloads", pattern = pattern, isIgnored = true)
@@ -91,7 +92,6 @@ internal fun MutableTWorkspace.generateK2IntentionTests() {
model("${idea}intentions/branched", pattern = pattern, isIgnored = true)
model("${idea}intentions/convertVariableAssignmentToExpression", pattern = pattern, isIgnored = true)
model("${idea}intentions/convertNullablePropertyToLateinit", pattern = pattern, isIgnored = true)
model("${idea}intentions/specifyExplicitLambdaSignature", pattern = pattern, isIgnored = true)
model("${idea}intentions/replaceSizeCheckWithIsNotEmpty", pattern = pattern, isIgnored = true)
model("${idea}intentions/replaceTypeArgumentWithUnderscore", pattern = pattern, isIgnored = true)
model("${idea}intentions/convertSecondaryConstructorToPrimary", pattern = pattern, isIgnored = true)