[kotlin] Migrate quick fixes to ModCommand API

#KTIJ-31809

GitOrigin-RevId: 7e2f3abac3478ed531cc7dfa25ff6e5a7ad2fe6b
This commit is contained in:
Andrey Cherkasov
2025-04-23 19:34:14 +00:00
committed by intellij-monorepo-bot
parent 429b92ac01
commit 8c927fe2af
8 changed files with 74 additions and 84 deletions
@@ -1,13 +1,13 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 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.ModCommandAction
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.analysis.api.resolution.singleConstructorCallOrNull
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.QuickFixActionBase
import org.jetbrains.kotlin.idea.quickfix.ActualAnnotationsNotMatchExpectFixFactoryCommon
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtAnnotationEntry
@@ -15,10 +15,10 @@ import org.jetbrains.kotlin.psi.KtNamedDeclaration
internal object ActualAnnotationsNotMatchExpectFixFactory {
val factory = KotlinQuickFixFactory.IntentionBased(::createQuickFixes)
val factory = KotlinQuickFixFactory.ModCommandBased(::createQuickFixes)
context (KaSession)
private fun createQuickFixes(diagnostic: KaFirDiagnostic.ActualAnnotationsNotMatchExpect): List<QuickFixActionBase<*>> {
private fun createQuickFixes(diagnostic: KaFirDiagnostic.ActualAnnotationsNotMatchExpect): List<ModCommandAction> {
val expectAnnotationEntry = diagnostic.incompatibilityType.expectAnnotation.psi as? KtAnnotationEntry
?: return emptyList()
@@ -32,7 +32,7 @@ internal object ActualAnnotationsNotMatchExpectFixFactory {
private fun createCopyAndReplaceAnnotationFixes(
diagnostic: KaFirDiagnostic.ActualAnnotationsNotMatchExpect,
expectAnnotationEntry: KtAnnotationEntry,
): List<QuickFixActionBase<*>> {
): List<ModCommandAction> {
val expectDeclaration = diagnostic.expectSymbol.psi as? KtNamedDeclaration ?: return emptyList()
val actualDeclaration = diagnostic.actualSymbol.psi as? KtNamedDeclaration ?: return emptyList()
val mappedIncompatibilityType = diagnostic.incompatibilityType.mapAnnotationType {
@@ -8,7 +8,7 @@ import org.jetbrains.kotlin.idea.inspections.RemoveAnnotationFix
internal object InapplicableJvmFieldFixFactories {
val removeAnnotationFixFactory = KotlinQuickFixFactory.IntentionBased { diagnostic: KaFirDiagnostic.InapplicableJvmField ->
val removeAnnotationFixFactory = KotlinQuickFixFactory.ModCommandBased { diagnostic: KaFirDiagnostic.InapplicableJvmField ->
listOf(
RemoveAnnotationFix(KotlinBundle.message("remove.jvmfield.annotation"), diagnostic.psi)
)
@@ -1,12 +1,11 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 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.modcommand.ModCommandAction
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.base.psi.callableIdIfNotLocal
import org.jetbrains.kotlin.idea.base.psi.classIdIfNonLocal
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.QuickFixActionBase
import org.jetbrains.kotlin.idea.inspections.RemoveAnnotationFix
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.*
@@ -23,7 +22,7 @@ object ActualAnnotationsNotMatchExpectFixFactoryCommon {
KtTypeReference::class,
)
fun createRemoveAnnotationFromExpectFix(expectAnnotationEntry: KtAnnotationEntry): QuickFixActionBase<*>? {
fun createRemoveAnnotationFromExpectFix(expectAnnotationEntry: KtAnnotationEntry): ModCommandAction? {
val annotationName = expectAnnotationEntry.shortName ?: return null
return RemoveAnnotationFix(
KotlinBundle.message("fix.remove.mismatched.annotation.from.expect.declaration.may.change.semantics", annotationName),
@@ -38,7 +37,7 @@ object ActualAnnotationsNotMatchExpectFixFactoryCommon {
actualAnnotationTargetElement: PsiElement?,
incompatibilityType: ExpectActualAnnotationsIncompatibilityType<KtAnnotationEntry?>,
annotationClassIdProvider: () -> ClassId?,
): List<KotlinQuickFixAction<*>> {
): List<ModCommandAction> {
if (skipFakeOverrideAndTypealias(expectDeclaration, actualDeclaration)) {
return emptyList()
}
@@ -73,7 +72,7 @@ object ActualAnnotationsNotMatchExpectFixFactoryCommon {
private fun createCopyFromExpectToActualFix(
expectAnnotationEntry: KtAnnotationEntry, actualAnnotationTargetElement: PsiElement?, annotationClassIdProvider: () -> ClassId?
): KotlinQuickFixAction<*>? {
): ModCommandAction? {
if (actualAnnotationTargetElement !is KtModifierListOwner) {
return null
}
@@ -1,35 +1,34 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 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.FileModifier.SafeFieldForPreview
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.PsiUpdateModCommandAction
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.util.addAnnotation
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtModifierListOwner
internal class CopyAnnotationFromExpectToActualFix(
actualElement: KtModifierListOwner,
@SafeFieldForPreview private val expectAnnotationEntry: KtAnnotationEntry,
@SafeFieldForPreview private val annotationClassId: ClassId,
) : KotlinQuickFixAction<KtModifierListOwner>(actualElement) {
private val expectAnnotationEntry: KtAnnotationEntry,
private val annotationClassId: ClassId,
) : PsiUpdateModCommandAction<KtModifierListOwner>(actualElement) {
private val expectAnnotationShortName: String = expectAnnotationEntry.shortName?.toString() ?: "<unknown>"
override fun getText(): String {
return KotlinBundle.message("fix.copy.mismatched.annotation.to.actual.declaration.may.change.semantics", expectAnnotationShortName)
}
override fun getFamilyName(): @IntentionFamilyName String =
KotlinBundle.message("fix.copy.mismatched.annotation.to.actual.declaration.may.change.semantics", expectAnnotationShortName)
override fun getFamilyName(): String = text
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val actualElement = element ?: return
override fun invoke(
context: ActionContext,
element: KtModifierListOwner,
updater: ModPsiUpdater,
) {
val innerText = expectAnnotationEntry.valueArguments.joinToString { it.asElement().text }
actualElement.addAnnotation(annotationClassId, innerText.takeIf { it.isNotEmpty() }, searchForExistingEntry = false)
element.addAnnotation(annotationClassId, innerText.takeIf { it.isNotEmpty() }, searchForExistingEntry = false)
}
}
@@ -1,64 +1,65 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.PsiUpdateModCommandAction
import org.jetbrains.annotations.Nls
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinPsiOnlyQuickFixAction
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.PsiElementSuitabilityCheckers
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.QuickFixesPsiBasedFactory
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclarationModifierList
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPropertyAccessor
class RemoveAnnotationFix(@Nls private val text: String, annotationEntry: KtAnnotationEntry) :
KotlinPsiOnlyQuickFixAction<KtAnnotationEntry>(annotationEntry) {
PsiUpdateModCommandAction<KtAnnotationEntry>(annotationEntry) {
override fun getText(): String = text
override fun getFamilyName(): @IntentionFamilyName String = text
override fun getFamilyName(): String = text
override fun invoke(
context: ActionContext,
element: KtAnnotationEntry,
updater: ModPsiUpdater,
): Unit = element.delete()
override fun getElementToMakeWritable(currentFile: PsiFile): PsiElement? = element
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
element?.delete()
object JvmOverloads :
QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
override fun doCreateQuickFix(psiElement: KtAnnotationEntry): List<IntentionAction> =
listOf(RemoveAnnotationFix(KotlinBundle.message("remove.jvmoverloads.annotation"), psiElement).asIntention())
}
object JvmOverloads : QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
object JvmField :
QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
override fun doCreateQuickFix(psiElement: KtAnnotationEntry): List<IntentionAction> =
listOf(RemoveAnnotationFix(KotlinBundle.message("remove.jvmoverloads.annotation"), psiElement))
listOf(RemoveAnnotationFix(KotlinBundle.message("remove.jvmfield.annotation"), psiElement).asIntention())
}
object JvmField : QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
object ExtensionFunctionType :
QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
override fun doCreateQuickFix(psiElement: KtAnnotationEntry): List<IntentionAction> =
listOf(RemoveAnnotationFix(KotlinBundle.message("remove.jvmfield.annotation"), psiElement))
}
object ExtensionFunctionType : QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
override fun doCreateQuickFix(psiElement: KtAnnotationEntry): List<IntentionAction> =
listOf(RemoveAnnotationFix(KotlinBundle.message("remove.extension.function.type.annotation"), psiElement))
listOf(RemoveAnnotationFix(KotlinBundle.message("remove.extension.function.type.annotation"), psiElement).asIntention())
}
object UseSiteGetDoesntHaveAnyEffect : AbstractUseSiteGetDoesntHaveAnyEffectQuickFixesFactory() {
override fun doCreateQuickFixImpl(psiElement: KtAnnotationEntry): IntentionAction =
RemoveAnnotationFix(KotlinBundle.message("remove.annotation.doesnt.have.any.effect"), psiElement)
RemoveAnnotationFix(KotlinBundle.message("remove.annotation.doesnt.have.any.effect"), psiElement).asIntention()
}
object RemoveForbiddenOptInRetention : QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
object RemoveForbiddenOptInRetention :
QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
override fun doCreateQuickFix(psiElement: KtAnnotationEntry): List<IntentionAction> =
listOf(RemoveAnnotationFix(KotlinBundle.message("fix.opt_in.remove.forbidden.retention"), psiElement))
listOf(RemoveAnnotationFix(KotlinBundle.message("fix.opt_in.remove.forbidden.retention"), psiElement).asIntention())
}
companion object : QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
companion object :
QuickFixesPsiBasedFactory<KtAnnotationEntry>(KtAnnotationEntry::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) {
override fun doCreateQuickFix(psiElement: KtAnnotationEntry): List<IntentionAction> =
listOf(RemoveAnnotationFix(KotlinBundle.message("fix.remove.annotation.text"), annotationEntry = psiElement))
listOf(RemoveAnnotationFix(KotlinBundle.message("fix.remove.annotation.text"), annotationEntry = psiElement).asIntention())
}
}
@@ -1,33 +1,25 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 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.FileModifier.SafeFieldForPreview
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.PsiUpdateModCommandAction
import org.jetbrains.annotations.Nls
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
internal class ReplaceAnnotationArgumentsInExpectActualFix(
@Nls private val text: String,
@SafeFieldForPreview private val copyFromAnnotationEntry: KtAnnotationEntry,
private val copyFromAnnotationEntry: KtAnnotationEntry,
copyToAnnotationEntry: KtAnnotationEntry,
) : KotlinQuickFixAction<KtAnnotationEntry>(copyToAnnotationEntry) {
override fun getText(): String = text
) : PsiUpdateModCommandAction<KtAnnotationEntry>(copyToAnnotationEntry) {
override fun getFamilyName(): @IntentionFamilyName String = text
override fun getFamilyName(): String = text
override fun getElementToMakeWritable(currentFile: PsiFile): PsiElement? = element
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val copyToAnnotationEntry = element ?: return
override fun invoke(context: ActionContext, element: KtAnnotationEntry, updater: ModPsiUpdater) {
val newValueArguments = copyFromAnnotationEntry.valueArgumentList?.copy()
copyToAnnotationEntry.valueArgumentList?.delete()
element.valueArgumentList?.delete()
if (newValueArguments != null) {
copyToAnnotationEntry.addAfter(newValueArguments, copyToAnnotationEntry.lastChild)
element.addAfter(newValueArguments, element.lastChild)
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 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.expectactual
import com.intellij.codeInsight.intention.IntentionAction
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.QuickFixActionBase
import org.jetbrains.kotlin.idea.quickfix.ActualAnnotationsNotMatchExpectFixFactoryCommon
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
import org.jetbrains.kotlin.load.kotlin.toSourceElement
@@ -33,7 +32,7 @@ internal object ActualAnnotationsNotMatchExpectFixFactory : KotlinIntentionActio
val removeAnnotationFix =
ActualAnnotationsNotMatchExpectFixFactoryCommon.createRemoveAnnotationFromExpectFix(expectAnnotationEntry)
return listOfNotNull(removeAnnotationFix) +
return listOfNotNull(removeAnnotationFix?.asIntention()) +
createCopyAndReplaceAnnotationFixes(expectAnnotationEntry, castedDiagnostic.a, castedDiagnostic.b,
castedDiagnostic.c, incompatibilityType)
}
@@ -44,7 +43,7 @@ internal object ActualAnnotationsNotMatchExpectFixFactory : KotlinIntentionActio
actualDeclarationDescriptor: DeclarationDescriptor,
actualAnnotationTargetSourceElement: Optional<SourceElement>,
incompatibilityType: ExpectActualAnnotationsIncompatibilityType<AnnotationDescriptor>,
): List<QuickFixActionBase<*>> {
): List<IntentionAction> {
val expectDeclaration = expectDeclarationDescriptor.toSourceElement.getPsi() as? KtNamedDeclaration ?: return emptyList()
val actualDeclaration = actualDeclarationDescriptor.toSourceElement.getPsi() as? KtNamedDeclaration ?: return emptyList()
val mappedIncompatibilityType = incompatibilityType.mapAnnotationType {
@@ -58,7 +57,7 @@ internal object ActualAnnotationsNotMatchExpectFixFactory : KotlinIntentionActio
actualAnnotationTargetSourceElement.orElse(null)?.getPsi(),
mappedIncompatibilityType,
annotationClassIdProvider = { getAnnotationClassId(expectAnnotationEntry) }
)
).map { it.asIntention() }
}
private fun getAnnotationClassId(annotationEntry: KtAnnotationEntry): ClassId? {
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2025 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.migration
import com.intellij.codeInsight.intention.IntentionAction
@@ -68,7 +68,7 @@ class MigrateExperimentalToRequiresOptInFix(
if (annotationDescriptor.fqName == FqNames.OptInFqNames.OLD_EXPERIMENTAL_FQ_NAME) {
val annotationOwner = annotationEntry.getStrictParentOfType<KtModifierListOwner>() ?: return null
if (annotationOwner.findAnnotation(OptInNames.REQUIRES_OPT_IN_FQ_NAME) != null)
return RemoveAnnotationFix(KotlinBundle.message("fix.opt_in.migrate.experimental.annotation.remove"), annotationEntry)
return RemoveAnnotationFix(KotlinBundle.message("fix.opt_in.migrate.experimental.annotation.remove"), annotationEntry).asIntention()
val requiresOptInInnerText = when (annotationDescriptor.getEnumValue("level")?.enumEntryName?.asString()) {
"ERROR" -> "level = RequiresOptIn.Level.ERROR"