[kotlin] Port AddFunModifierFix to K2

This quick fix is for the INTERFACE_AS_FUNCTION error.

^KTIJ-26890

GitOrigin-RevId: 57e2171b1ee82ca4202edd69aa3ed19d26ca2c46
This commit is contained in:
Andrey Cherkasov
2024-03-21 01:10:22 +00:00
committed by intellij-monorepo-bot
parent 78fcccc1a7
commit 8d0b39fe2d
14 changed files with 157 additions and 9 deletions
@@ -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, AddFunModifierFix.ElementContext>(ktClass, context) {
class ElementContext(val referrerCallPointer: SmartPsiElementPointer<KtCallExpression>) : 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<KtValueArgument>()?.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<KtSymbolWithModality>()
.filter { it.modality == Modality.ABSTRACT }
.singleOrNull() ?: return false
return singleAbstractMember is KtFunctionSymbol && singleAbstractMember.typeParameters.isEmpty()
}
@@ -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 {
@@ -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");
}
}
@@ -9,4 +9,5 @@ fun test() {
val x = foo(<caret>I {}, 2)
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo({}, 2)
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo(<caret>I {})
}
/* IGNORE_K2 */
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo {}
}
/* IGNORE_K2 */
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo({}, <caret>I {})
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo({}, {})
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo(i = <caret>I {})
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -9,4 +9,5 @@ fun test() {
val x = foo(i = {})
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -7,4 +7,5 @@ fun test() {
val x = <caret>I {}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -7,4 +7,5 @@ fun test() {
val x = I {}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.AddFunModifierFixFactory$AddFunModifierFix
@@ -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)