From e2a67ba0dc3e939a5a237f20ecceba3ac72d2220 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Sat, 10 Sep 2022 18:22:11 +0900 Subject: [PATCH] [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 --- .../messages/KotlinBundle.properties | 2 + .../kotlin/idea/quickfix/AddFunModifierFix.kt | 82 +++++++++++++++++++ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 +- .../idea/quickfix/QuickFixTestGenerated.java | 48 +++++++++++ .../addFunModifier/asFirstArgument.kt | 10 +++ .../addFunModifier/asFirstArgument.kt.after | 10 +++ .../quickfix/addFunModifier/asLastArgument.kt | 10 +++ .../addFunModifier/asLastArgument.kt.after | 10 +++ .../quickfix/addFunModifier/hasArguments.kt | 12 +++ .../quickfix/addFunModifier/hasNoArguments.kt | 11 +++ .../quickfix/addFunModifier/notSam.kt | 11 +++ .../quickfix/addFunModifier/notSam2.kt | 12 +++ .../quickfix/addFunModifier/notSam3.kt | 11 +++ .../quickfix/addFunModifier/simple.kt | 8 ++ .../quickfix/addFunModifier/simple.kt.after | 8 ++ .../quickfix/allowResolveInWriteAction.txt | 3 +- 16 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunModifierFix.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasArguments.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasNoArguments.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam2.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam3.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties index b35639acc63a..d79fdfde7382 100644 --- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties +++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties @@ -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}'' diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunModifierFix.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunModifierFix.kt new file mode 100644 index 000000000000..734dd4f3c6fb --- /dev/null +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunModifierFix.kt @@ -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 +) : 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()?.takeIf { it.getArgumentExpression() == this } ?: return + val parentCall = argument.getStrictParentOfType() ?: 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() + } + } +} diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 89aa68e90991..99d447a416e5 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -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) diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 5f5fbcb8a67a..a59b9c0b53e8 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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 { diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt new file mode 100644 index 000000000000..4c2054f2d15d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt @@ -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(I {}, 2) +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after new file mode 100644 index 000000000000..2e942115b02d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asFirstArgument.kt.after @@ -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) +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt new file mode 100644 index 000000000000..10a49d5502e8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt @@ -0,0 +1,10 @@ +// "Add 'fun' modifier to 'I'" "true" +interface I { + fun f() +} + +fun foo(i: I) {} + +fun test() { + val x = foo(I {}) +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after new file mode 100644 index 000000000000..8371a518da12 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/asLastArgument.kt.after @@ -0,0 +1,10 @@ +// "Add 'fun' modifier to 'I'" "true" +fun interface I { + fun f() +} + +fun foo(i: I) {} + +fun test() { + val x = foo {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasArguments.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasArguments.kt new file mode 100644 index 000000000000..4a3e450dbab4 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasArguments.kt @@ -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 = I(1) {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasNoArguments.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasNoArguments.kt new file mode 100644 index 000000000000..2bbe8d89ffdf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/hasNoArguments.kt @@ -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 = I() +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam.kt new file mode 100644 index 000000000000..07fd6d2be588 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam.kt @@ -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 = I {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam2.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam2.kt new file mode 100644 index 000000000000..99d7186c5f43 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam2.kt @@ -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 = I {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam3.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam3.kt new file mode 100644 index 000000000000..cc66ab937e96 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/notSam3.kt @@ -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 = I {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt new file mode 100644 index 000000000000..192ec8645ee9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt @@ -0,0 +1,8 @@ +// "Add 'fun' modifier to 'I'" "true" +interface I { + fun f() +} + +fun test() { + val x = I {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after new file mode 100644 index 000000000000..2eb03383a1b1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/addFunModifier/simple.kt.after @@ -0,0 +1,8 @@ +// "Add 'fun' modifier to 'I'" "true" +fun interface I { + fun f() +} + +fun test() { + val x = I {} +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/allowResolveInWriteAction.txt b/plugins/kotlin/idea/tests/testData/quickfix/allowResolveInWriteAction.txt index 8adfbc9848b3..00a2f8ffdab6 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/allowResolveInWriteAction.txt +++ b/plugins/kotlin/idea/tests/testData/quickfix/allowResolveInWriteAction.txt @@ -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 \ No newline at end of file +org.jetbrains.kotlin.idea.quickfix.AbstractSuperCallFix +org.jetbrains.kotlin.idea.quickfix.AddFunModifierFix