diff --git a/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AddFunModifierFixFactory.kt b/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AddFunModifierFixFactory.kt new file mode 100644 index 000000000000..e4788940e6b1 --- /dev/null +++ b/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AddFunModifierFixFactory.kt @@ -0,0 +1,76 @@ +// 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.fixes + +import com.intellij.modcommand.ActionContext +import com.intellij.modcommand.ModPsiUpdater +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.refactoring.suggested.createSmartPointer +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings +import org.jetbrains.kotlin.idea.base.psi.replaceSamConstructorCall +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinModCommandAction +import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtLambdaArgument +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +internal object AddFunModifierFixFactory { + val addFunModifierFixFactory = KotlinQuickFixFactory.ModCommandBased { diagnostic: KtFirDiagnostic.InterfaceAsFunction -> + val referrer = diagnostic.psi + if (referrer.languageVersionSettings.languageVersion < LanguageVersion.KOTLIN_1_4) return@ModCommandBased emptyList() + + val referrerCall = referrer.parent as? KtCallExpression ?: return@ModCommandBased emptyList() + if (referrerCall.valueArguments.singleOrNull() !is KtLambdaArgument) return@ModCommandBased emptyList() + val referenceClassSymbol = diagnostic.classSymbol as? KtNamedClassOrObjectSymbol ?: return@ModCommandBased emptyList() + if (referenceClassSymbol.isFun || !referenceClassSymbol.isSamInterface()) return@ModCommandBased emptyList() + + val referenceClass = referenceClassSymbol.psi as? KtClass ?: return@ModCommandBased emptyList() + val referenceClassName = referenceClass.name ?: return@ModCommandBased emptyList() + + return@ModCommandBased listOf( + AddFunModifierFix( + referenceClass, + referenceClassName, + AddFunModifierFix.ElementContext(referrerCall.createSmartPointer()) + ) + ) + } + + private class AddFunModifierFix(ktClass: KtClass, private val elementName: String, context: ElementContext) : + KotlinModCommandAction(ktClass, context) { + + class ElementContext(val referrerCallPointer: SmartPsiElementPointer) : KotlinModCommandAction.ElementContext + + override fun invoke(context: ActionContext, element: KtClass, elementContext: ElementContext, updater: ModPsiUpdater) { + val referrerCall = updater.getWritable(elementContext.referrerCallPointer.element) + element.addModifier(KtTokens.FUN_KEYWORD) + if (referrerCall?.lambdaArguments?.singleOrNull() == null) return + referrerCall.getStrictParentOfType()?.takeIf { it.getArgumentExpression() == referrerCall } ?: return + replaceSamConstructorCall(referrerCall) + } + + override fun getFamilyName() = KotlinBundle.message("add.fun.modifier.to.0", elementName) + } +} + +context(KtAnalysisSession) +private fun KtNamedClassOrObjectSymbol.isSamInterface(): Boolean { + if (classKind != KtClassKind.INTERFACE) return false + val singleAbstractMember = getMemberScope() + .getCallableSymbols() + .filterIsInstance() + .filter { it.modality == Modality.ABSTRACT } + .singleOrNull() ?: return false + return singleAbstractMember is KtFunctionSymbol && singleAbstractMember.typeParameters.isEmpty() +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/KotlinK2QuickFixRegistrar.kt b/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/KotlinK2QuickFixRegistrar.kt index 63df42944577..5866abfa7c67 100644 --- a/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/KotlinK2QuickFixRegistrar.kt +++ b/plugins/kotlin/code-insight/fixes-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/KotlinK2QuickFixRegistrar.kt @@ -85,6 +85,7 @@ class KotlinK2QuickFixRegistrar : KotlinQuickFixRegistrar() { registerPsiQuickFixes(KtFirDiagnostic.ValOrVarOnSecondaryConstructorParameter::class, RemoveValVarFromParameterFix) registerFactory(MakeSuperTypeOpenFixFactory.makeSuperTypeOpenFixFactory) registerFactory(MakeSuperTypeOpenFixFactory.makeUpperBoundOpenFixFactory) + registerFactory(AddFunModifierFixFactory.addFunModifierFixFactory) } private val addAbstract = KtQuickFixesListBuilder.registerPsiQuickFix { diff --git a/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/HighLevelQuickFixTestGenerated.java b/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/HighLevelQuickFixTestGenerated.java index 2c24fc9139cb..67866b21000c 100644 --- a/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/HighLevelQuickFixTestGenerated.java +++ b/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/HighLevelQuickFixTestGenerated.java @@ -4576,7 +4576,68 @@ public abstract class HighLevelQuickFixTestGenerated extends AbstractHighLevelQu + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/quickfix/addFunModifier") + public static class AddFunModifier extends AbstractHighLevelQuickFixTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + @TestMetadata("asFirstArgument.kt") + public void testAsFirstArgument() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt"); + } + + @TestMetadata("asLastArgument.kt") + public void testAsLastArgument() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt"); + } + + @TestMetadata("hasArguments.kt") + public void testHasArguments() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/hasArguments.kt"); + } + + @TestMetadata("hasNoArguments.kt") + public void testHasNoArguments() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/hasNoArguments.kt"); + } + + @TestMetadata("multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt") + public void testMultipleLambdaArgumentsAreNotMovedOutsideParentheses() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt"); + } + + @TestMetadata("namedLambdaArgumentIsNotMovedOutsideParentheses.kt") + public void testNamedLambdaArgumentIsNotMovedOutsideParentheses() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt"); + } + + @TestMetadata("nonSource.kt") + public void testNonSource() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/nonSource.kt"); + } + + @TestMetadata("notSam.kt") + public void testNotSam() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/notSam.kt"); + } + + @TestMetadata("notSam2.kt") + public void testNotSam2() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/notSam2.kt"); + } + + @TestMetadata("notSam3.kt") + public void testNotSam3() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/notSam3.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("../../../idea/tests/testData/quickfix/addFunModifier/simple.kt"); + } + } diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt index 90d98378f569..d2dd52122595 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt @@ -9,4 +9,5 @@ fun test() { val x = foo(I {}, 2) } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after index f1a39466e5a2..65cdd75d9c94 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after @@ -9,4 +9,5 @@ fun test() { val x = foo({}, 2) } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt index 85635d7cec72..4b8ebec9030f 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt @@ -9,4 +9,5 @@ fun test() { val x = foo(I {}) } +/* IGNORE_K2 */ // FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after index ac1a723560a9..99dd9b0d3b1c 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after @@ -9,4 +9,5 @@ fun test() { val x = foo {} } +/* IGNORE_K2 */ // FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt index b5eef41481ac..ce6200a4f034 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt @@ -9,4 +9,5 @@ fun test() { val x = foo({}, I {}) } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt.after index aac6380d88a1..a1e6da5a4372 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt.after +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/multipleLambdaArgumentsAreNotMovedOutsideParentheses.kt.after @@ -9,4 +9,5 @@ fun test() { val x = foo({}, {}) } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt index 5dd7d03efec9..01ebc37345d3 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt @@ -9,4 +9,5 @@ fun test() { val x = foo(i = I {}) } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt.after index 20d280a99ed2..eec3dfff77f4 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt.after +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/namedLambdaArgumentIsNotMovedOutsideParentheses.kt.after @@ -9,4 +9,5 @@ fun test() { val x = foo(i = {}) } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt index 85ece30861da..dc3363c5a25d 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt @@ -7,4 +7,5 @@ fun test() { val x = I {} } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after index 07c4ffccc0c6..f246d557a39d 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after @@ -7,4 +7,5 @@ fun test() { val x = I {} } -// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix \ No newline at end of file +// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix +// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix \ No newline at end of file diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2QuickFixTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2QuickFixTests.kt index f2b737fb3b12..5d7019f8cfe2 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2QuickFixTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2QuickFixTests.kt @@ -63,7 +63,7 @@ internal fun MutableTWorkspace.generateK2FixTests() { model("$idea/quickfix/addElseBranchToIf", pattern = pattern, isIgnored = true) model("$idea/quickfix/addEmptyArgumentList", pattern = pattern, isIgnored = true) model("$idea/quickfix/addEqEqTrue", pattern = pattern, isIgnored = true) - model("$idea/quickfix/addFunModifier", pattern = pattern, isIgnored = true) + model("$idea/quickfix/addFunModifier", pattern = pattern) model("$idea/quickfix/addGenericUpperBound", pattern = pattern, isIgnored = true) model("$idea/quickfix/addInline", pattern = pattern, isIgnored = true) model("$idea/quickfix/addInlineToReifiedFunctionFix", pattern = pattern, isIgnored = true)