[kotlin] Introduce "Add 'fun' modifier" quick-fix for RESOLUTION_TO_CLASSIFIER

^KTIJ-16332 Fixed

closes https://github.com/JetBrains/intellij-community/pull/2155

GitOrigin-RevId: 373f07b8aa05e4bfb5da513a05810dcae5e4230d
This commit is contained in:
Toshiaki Kameyama
2022-11-01 06:40:55 +00:00
committed by intellij-monorepo-bot
parent 5d04acf825
commit e2a67ba0dc
16 changed files with 248 additions and 2 deletions
@@ -2523,3 +2523,5 @@ inspection.convert.object.to.data.object.display.name=Convert 'object' to 'data
inspection.replace.with.string.builder.append.range.display.name='StringBuilder.append(CharArray, offset, len)' call on the JVM
progress.title.collect.hierarchy=Collect ''{0}'' hierarchy ...
add.fun.modifier.to.0=Add ''fun'' modifier to ''{0}''
@@ -0,0 +1,82 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.refactoring.suggested.createSmartPointer
import org.jetbrains.kotlin.backend.jvm.ir.psiElement
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.core.canMoveLambdaOutsideParentheses
import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses
import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection
import org.jetbrains.kotlin.idea.util.application.runWriteActionIfPhysical
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
import org.jetbrains.kotlin.resolve.sam.getAbstractMembers
class AddFunModifierFix(
element: KtClass,
private val elementName: String,
private val referrerCall: SmartPsiElementPointer<KtCallExpression>
) : AddModifierFixFE10(element, KtTokens.FUN_KEYWORD) {
override fun getText() = KotlinBundle.message("add.fun.modifier.to.0", elementName)
override fun getFamilyName() = text
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
super.invokeImpl(project, editor, file)
referrerCall.element?.removeRedundantSamConstructor()
}
private fun KtCallExpression.removeRedundantSamConstructor() {
if (lambdaArguments.singleOrNull() == null) return
val argument = getStrictParentOfType<KtValueArgument>()?.takeIf { it.getArgumentExpression() == this } ?: return
val parentCall = argument.getStrictParentOfType<KtCallExpression>() ?: return
RedundantSamConstructorInspection.replaceSamConstructorCall(this)
if (parentCall.canMoveLambdaOutsideParentheses()) {
runWriteActionIfPhysical(parentCall) {
parentCall.moveFunctionLiteralOutsideParentheses()
}
}
}
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val casted = Errors.RESOLUTION_TO_CLASSIFIER.cast(diagnostic)
val referrer = casted.psiElement
if (referrer.languageVersionSettings.languageVersion < LanguageVersion.KOTLIN_1_4) return null
val referrerCall = referrer.parent as? KtCallExpression ?: return null
if (referrerCall.valueArguments.singleOrNull() !is KtLambdaArgument) return null
val referenceClassDescriptor = casted.a as? ClassDescriptor ?: return null
if (referenceClassDescriptor.isFun || !referenceClassDescriptor.isSamInterface()) return null
val referenceClass = referenceClassDescriptor.psiElement as? KtClass ?: return null
val referenceClassName = referenceClass.name ?: return null
return AddFunModifierFix(referenceClass, referenceClassName, referrerCall.createSmartPointer())
}
private fun ClassDescriptor.isSamInterface(): Boolean {
if (kind != ClassKind.INTERFACE) return false
val singleAbstractMember = getAbstractMembers(this).singleOrNull() ?: return false
return singleAbstractMember is SimpleFunctionDescriptor && singleAbstractMember.typeParameters.isEmpty()
}
}
}
@@ -678,7 +678,7 @@ class QuickFixRegistrar : QuickFixContributor {
DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE.registerFactory(RemoveDefaultParameterValueFix)
ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS.registerFactory(RemoveDefaultParameterValueFix)
RESOLUTION_TO_CLASSIFIER.registerFactory(ConvertToAnonymousObjectFix)
RESOLUTION_TO_CLASSIFIER.registerFactory(ConvertToAnonymousObjectFix, AddFunModifierFix)
NOTHING_TO_INLINE.registerFactory(RemoveModifierFixBase.removeNonRedundantModifier)
@@ -1002,6 +1002,54 @@ public abstract class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/quickfix/addFunModifier")
public static class AddFunModifier extends AbstractQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("asFirstArgument.kt")
public void testAsFirstArgument() throws Exception {
runTest("testData/quickfix/addFunModifier/asFirstArgument.kt");
}
@TestMetadata("asLastArgument.kt")
public void testAsLastArgument() throws Exception {
runTest("testData/quickfix/addFunModifier/asLastArgument.kt");
}
@TestMetadata("hasArguments.kt")
public void testHasArguments() throws Exception {
runTest("testData/quickfix/addFunModifier/hasArguments.kt");
}
@TestMetadata("hasNoArguments.kt")
public void testHasNoArguments() throws Exception {
runTest("testData/quickfix/addFunModifier/hasNoArguments.kt");
}
@TestMetadata("notSam.kt")
public void testNotSam() throws Exception {
runTest("testData/quickfix/addFunModifier/notSam.kt");
}
@TestMetadata("notSam2.kt")
public void testNotSam2() throws Exception {
runTest("testData/quickfix/addFunModifier/notSam2.kt");
}
@TestMetadata("notSam3.kt")
public void testNotSam3() throws Exception {
runTest("testData/quickfix/addFunModifier/notSam3.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("testData/quickfix/addFunModifier/simple.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/quickfix/addGenericUpperBound")
public static class AddGenericUpperBound extends AbstractQuickFixTest {
@@ -0,0 +1,10 @@
// "Add 'fun' modifier to 'I'" "true"
interface I {
fun f()
}
fun foo(i: I, j: Int) {}
fun test() {
val x = foo(<caret>I {}, 2)
}
@@ -0,0 +1,10 @@
// "Add 'fun' modifier to 'I'" "true"
fun interface I {
fun f()
}
fun foo(i: I, j: Int) {}
fun test() {
val x = foo({}, 2)
}
@@ -0,0 +1,10 @@
// "Add 'fun' modifier to 'I'" "true"
interface I {
fun f()
}
fun foo(i: I) {}
fun test() {
val x = foo(<caret>I {})
}
@@ -0,0 +1,10 @@
// "Add 'fun' modifier to 'I'" "true"
fun interface I {
fun f()
}
fun foo(i: I) {}
fun test() {
val x = foo {}
}
@@ -0,0 +1,12 @@
// "Add 'fun' modifier to 'I'" "false"
// DISABLE-ERRORS
// ACTION: Convert to anonymous object
// ACTION: Introduce import alias
// ACTION: Split property declaration
interface I {
fun f()
}
fun test() {
val x = <caret>I(1) {}
}
@@ -0,0 +1,11 @@
// "Add 'fun' modifier to 'I'" "false"
// DISABLE-ERRORS
// ACTION: Introduce import alias
// ACTION: Split property declaration
interface I {
fun f()
}
fun test() {
val x = <caret>I()
}
@@ -0,0 +1,11 @@
// "Add 'fun' modifier to 'I'" "false"
// DISABLE-ERRORS
// ACTION: Introduce import alias
// ACTION: Split property declaration
interface I {
fun f() {}
}
fun test() {
val x = <caret>I {}
}
@@ -0,0 +1,12 @@
// "Add 'fun' modifier to 'I'" "false"
// DISABLE-ERRORS
// ACTION: Introduce import alias
// ACTION: Split property declaration
interface I {
fun f()
fun g()
}
fun test() {
val x = <caret>I {}
}
@@ -0,0 +1,11 @@
// "Add 'fun' modifier to 'I'" "false"
// DISABLE-ERRORS
// ACTION: Introduce import alias
// ACTION: Split property declaration
interface I {
fun <T> f()
}
fun test() {
val x = <caret>I {}
}
@@ -0,0 +1,8 @@
// "Add 'fun' modifier to 'I'" "true"
interface I {
fun f()
}
fun test() {
val x = <caret>I {}
}
@@ -0,0 +1,8 @@
// "Add 'fun' modifier to 'I'" "true"
fun interface I {
fun f()
}
fun test() {
val x = I {}
}
@@ -80,4 +80,5 @@ com.intellij.spring.model.highlighting.autowire.SpringUastInjectionPointsAutowir
org.jetbrains.kotlin.idea.compilerPlugin.parcelize.quickfixes.ParcelMigrateToParcelizeQuickFix
org.jetbrains.kotlin.idea.quickfix.AddConversionCallFix
org.jetbrains.kotlin.idea.quickfix.OverloadResolutionChangeFix
org.jetbrains.kotlin.idea.quickfix.AbstractSuperCallFix
org.jetbrains.kotlin.idea.quickfix.AbstractSuperCallFix
org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix