[mod-commands] IDEA-333040 Convert CompositeIntentionQuickFix to ModCommand

GitOrigin-RevId: 67fd2853544b99e94e442566d29d874990292d9f
This commit is contained in:
Tagir Valeev
2023-09-21 16:08:02 +00:00
committed by intellij-monorepo-bot
parent c86d97746e
commit 83ad94ff1f
10 changed files with 102 additions and 156 deletions
@@ -6,8 +6,21 @@ import org.jetbrains.annotations.NotNull;
public interface ChangeModifierRequest extends ActionRequest {
/**
* @return the modifier which should be changed
*/
@NotNull
JvmModifier getModifier();
/**
* @return true if the modifier should be added, false if it should be removed
*/
boolean shouldBePresent();
/**
* @return true if it's desired to process hierarchy when applicable (e.g., update overriding methods correspondingly)
*/
default boolean processHierarchy() {
return true;
}
}
@@ -3,8 +3,10 @@ package com.intellij.lang.jvm.actions
import com.intellij.lang.jvm.JvmModifier
fun modifierRequest(modifier: JvmModifier, shouldBePresent: Boolean): ChangeModifierRequest = object : ChangeModifierRequest {
@JvmOverloads
fun modifierRequest(modifier: JvmModifier, shouldBePresent: Boolean, processHierarchy: Boolean = true): ChangeModifierRequest = object : ChangeModifierRequest {
override fun isValid(): Boolean = true
override fun getModifier(): JvmModifier = modifier
override fun shouldBePresent(): Boolean = shouldBePresent
override fun processHierarchy(): Boolean = processHierarchy
}
@@ -37,6 +37,7 @@ public class ModifierFix extends LocalQuickFixAndIntentionActionOnPsiElement imp
@PsiModifier.ModifierConstant private final String myModifier;
private final boolean myShouldHave;
private final boolean myShowContainingClass;
private final boolean myProcessHierarchy;
private volatile @IntentionName String myName;
private final boolean myStartInWriteAction;
@@ -49,6 +50,7 @@ public class ModifierFix extends LocalQuickFixAndIntentionActionOnPsiElement imp
myShouldHave = shouldHave;
myShowContainingClass = showContainingClass;
myName = format(null, modifierList, myShowContainingClass);
myProcessHierarchy = true;
myStartInWriteAction = !(modifierList.getParent() instanceof PsiMethod) || AccessModifier.fromPsiModifier(modifier) == null;
}
@@ -56,13 +58,21 @@ public class ModifierFix extends LocalQuickFixAndIntentionActionOnPsiElement imp
@PsiModifier.ModifierConstant @NotNull String modifier,
boolean shouldHave,
boolean showContainingClass) {
this(owner, modifier, shouldHave, showContainingClass, true);
}
public ModifierFix(@NotNull PsiModifierListOwner owner,
@PsiModifier.ModifierConstant @NotNull String modifier,
boolean shouldHave,
boolean showContainingClass, boolean processHierarchy) {
super(owner);
myModifier = modifier;
myShouldHave = shouldHave;
myShowContainingClass = showContainingClass;
myProcessHierarchy = processHierarchy;
PsiVariable variable = owner instanceof PsiVariable ? (PsiVariable)owner : null;
myName = format(variable, owner.getModifierList(), myShowContainingClass);
myStartInWriteAction = !(owner instanceof PsiMethod) || AccessModifier.fromPsiModifier(modifier) == null;
myStartInWriteAction = !myProcessHierarchy || !(owner instanceof PsiMethod) || AccessModifier.fromPsiModifier(modifier) == null;
}
private @IntentionName @NotNull String format(PsiVariable variable, PsiModifierList modifierList, boolean showContainingClass) {
@@ -84,7 +84,7 @@ class JavaElementActionsFactory : JvmElementActionsFactory() {
internal class ChangeModifierFix(declaration: PsiModifierListOwner,
@FileModifier.SafeFieldForPreview val request: ChangeModifierRequest) :
ModifierFix(declaration, request.modifier.toPsiModifier(), request.shouldBePresent(), true) {
ModifierFix(declaration, request.modifier.toPsiModifier(), request.shouldBePresent(), true, request.processHierarchy()) {
override fun isAvailable(): Boolean = request.isValid && super.isAvailable()
override fun isAvailable(project: Project,
@@ -1,54 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.fix
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.nonPreviewElement
import com.intellij.lang.jvm.JvmModifiersOwner
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.SmartPointerManager
import com.intellij.util.asSafely
/**
* A quickfix that can call multiple JVM intention actions and bundle them into a single quick fix.
*/
abstract class CompositeIntentionQuickFix : LocalQuickFix {
override fun startInWriteAction(): Boolean = false
protected fun generatePreviews(project: Project, previewDescriptor: ProblemDescriptor, element: PsiElement): IntentionPreviewInfo {
val containingFile = previewDescriptor.startElement.containingFile ?: return IntentionPreviewInfo.EMPTY
val editor = IntentionPreviewUtils.getPreviewEditor() ?: return IntentionPreviewInfo.EMPTY
val target = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(element)
getActions(project, previewDescriptor).forEach { factory ->
val actions = factory(target.element?.nonPreviewElement ?: return@forEach)
actions.forEach { action ->
action.generatePreview(project, editor, containingFile)
}
}
return IntentionPreviewInfo.DIFF
}
protected fun applyFixes(project: Project, descriptor: ProblemDescriptor, element: PsiElement) {
val containingFile = descriptor.psiElement.containingFile ?: return
val target = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(element)
if (!FileModificationService.getInstance().prepareFileForWrite(element.containingFile)) return
getActions(project, descriptor).forEach { factory ->
val actions = factory(target.element.asSafely<JvmModifiersOwner>() ?: return@forEach)
actions.forEach { action ->
if (action.startInWriteAction()) {
runWriteAction { action.invoke(project, null, containingFile) }
} else {
action.invoke(project, null, containingFile)
}
}
}
}
protected abstract fun getActions(project: Project, descriptor: ProblemDescriptor): List<(JvmModifiersOwner) -> List<IntentionAction>>
}
@@ -0,0 +1,30 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.fix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.lang.jvm.JvmModifiersOwner
import com.intellij.modcommand.PsiUpdateModCommandQuickFix
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPointerManager
import com.intellij.util.asSafely
/**
* A quickfix that can call multiple JVM intention actions and bundle them into a single quick fix.
* It's assumed that all the JVM intention actions are well-behaving: they only modify the single PSI file
* and don't do anything else like starting template.
*/
abstract class CompositeModCommandQuickFix : PsiUpdateModCommandQuickFix() {
protected fun applyFixes(project: Project, element: PsiElement, containingFile: PsiFile) {
val target = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(element)
getActions(project).forEach { factory ->
val actions = factory(target.element.asSafely<JvmModifiersOwner>() ?: return@forEach)
actions.forEach { action ->
action.invoke(project, null, containingFile)
}
}
}
protected abstract fun getActions(project: Project): List<(JvmModifiersOwner) -> List<IntentionAction>>
}
@@ -8,10 +8,9 @@ import com.intellij.codeInsight.MetaAnnotationUtil
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil
import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInsight.options.JavaClassValidator
import com.intellij.codeInspection.*
import com.intellij.codeInspection.fix.CompositeIntentionQuickFix
import com.intellij.codeInspection.fix.CompositeModCommandQuickFix
import com.intellij.codeInspection.options.OptPane
import com.intellij.codeInspection.options.OptPane.pane
import com.intellij.codeInspection.options.OptPane.stringList
@@ -24,6 +23,7 @@ import com.intellij.lang.jvm.JvmModifiersOwner
import com.intellij.lang.jvm.actions.*
import com.intellij.lang.jvm.types.JvmPrimitiveTypeKind
import com.intellij.lang.jvm.types.JvmType
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
@@ -333,8 +333,8 @@ private class JUnitMalformedSignatureVisitor(
) {
override fun getName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.class.signature.multi")
override fun getActions(project: Project, descriptor: ProblemDescriptor): List<(JvmModifiersOwner) -> List<IntentionAction>> {
val list = super.getActions(project, descriptor).toMutableList()
override fun getActions(project: Project): List<(JvmModifiersOwner) -> List<IntentionAction>> {
val list = super.getActions(project).toMutableList()
list.add { owner ->
val outerClass = owner.sourceElement?.toUElementOfType<UClass>()?.nestedClassHierarchy()?.last()!!
val request = annotationRequest(ORG_JUNIT_RUNNER_RUN_WITH, classAttribute("value", ORG_JUNIT_EXPERIMENTAL_RUNNERS_ENCLOSED))
@@ -345,9 +345,10 @@ private class JUnitMalformedSignatureVisitor(
override fun getFamilyName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.class.signature.multi")
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val uClass = getUParentForIdentifier(descriptor.psiElement)?.asSafely<UClass>() ?: return
applyFixes(project, descriptor, uClass.javaPsi.asSafely<PsiClass>() ?: return)
override fun applyFix(project: Project, element: PsiElement, updater: ModPsiUpdater) {
val uClass = getUParentForIdentifier(element)?.asSafely<UClass>() ?: return
applyFixes(project, uClass.javaPsi.asSafely<PsiClass>() ?: return,
element.containingFile ?: return)
}
}
@@ -1118,22 +1119,18 @@ private class JUnitMalformedSignatureVisitor(
private val makeStatic: Boolean? = null,
private val makePublic: Boolean? = null,
private val annotation: String? = null
) : CompositeIntentionQuickFix() {
) : CompositeModCommandQuickFix() {
override fun getFamilyName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.class.signature")
override fun getName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.class.signature.descriptor", name)
override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo {
val javaDeclaration = getUParentForIdentifier(previewDescriptor.psiElement)?.asSafely<UClass>() ?: return IntentionPreviewInfo.EMPTY
return generatePreviews(project, previewDescriptor, javaDeclaration.javaPsi.asSafely<PsiClass>() ?: return IntentionPreviewInfo.EMPTY)
override fun applyFix(project: Project, element: PsiElement, updater: ModPsiUpdater) {
val javaDeclaration = getUParentForIdentifier(element)?.asSafely<UClass>() ?: return
applyFixes(project, javaDeclaration.javaPsi.asSafely<PsiClass>() ?: return,
element.containingFile ?: return)
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val javaDeclaration = getUParentForIdentifier(descriptor.psiElement)?.asSafely<UClass>() ?: return
applyFixes(project, descriptor, javaDeclaration.javaPsi.asSafely<PsiClass>() ?: return)
}
override fun getActions(project: Project, descriptor: ProblemDescriptor): List<(JvmModifiersOwner) -> List<IntentionAction>> {
override fun getActions(project: Project): List<(JvmModifiersOwner) -> List<IntentionAction>> {
val actions = mutableListOf<(JvmModifiersOwner) -> List<IntentionAction>>()
if (makeStatic != null) {
actions.add { jvmClass -> createModifierActions(jvmClass, modifierRequest(JvmModifier.STATIC, makeStatic)) }
@@ -1152,22 +1149,17 @@ private class JUnitMalformedSignatureVisitor(
private val name: @NlsSafe String,
private val makeStatic: Boolean?,
private val newVisibility: JvmModifier? = null
) : CompositeIntentionQuickFix() {
) : CompositeModCommandQuickFix() {
override fun getFamilyName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.field.signature")
override fun getName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.field.signature.descriptor", name)
override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo {
val javaDeclaration = getUParentForIdentifier(previewDescriptor.psiElement)?.asSafely<UField>() ?: return IntentionPreviewInfo.EMPTY
return generatePreviews(project, previewDescriptor, javaDeclaration.javaPsi ?: return IntentionPreviewInfo.EMPTY)
override fun applyFix(project: Project, element: PsiElement, updater: ModPsiUpdater) {
val javaDeclaration = getUParentForIdentifier(element)?.asSafely<UField>() ?: return
applyFixes(project, javaDeclaration.javaPsi ?: return, element.containingFile ?: return)
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val javaDeclaration = getUParentForIdentifier(descriptor.psiElement)?.asSafely<UField>() ?: return
applyFixes(project, descriptor, javaDeclaration.javaPsi ?: return)
}
override fun getActions(project: Project, descriptor: ProblemDescriptor): List<(JvmModifiersOwner) -> List<IntentionAction>> {
override fun getActions(project: Project): List<(JvmModifiersOwner) -> List<IntentionAction>> {
val actions = mutableListOf<(JvmModifiersOwner) -> List<IntentionAction>>()
if (newVisibility != null) {
actions.add { jvmField -> createModifierActions(jvmField, modifierRequest(newVisibility, true)) }
@@ -1185,22 +1177,17 @@ private class JUnitMalformedSignatureVisitor(
private val shouldBeVoidType: Boolean? = null,
private val newVisibility: JvmModifier? = null,
@SafeFieldForPreview private val inCorrectParams: Map<String, JvmType>? = null
) : CompositeIntentionQuickFix() {
) : CompositeModCommandQuickFix() {
override fun getFamilyName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.method.signature")
override fun getName(): String = JvmAnalysisBundle.message("jvm.inspections.junit.malformed.fix.method.signature.descriptor", name)
override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo {
val javaDeclaration = getUParentForIdentifier(previewDescriptor.psiElement)?.asSafely<UMethod>() ?: return IntentionPreviewInfo.EMPTY
return generatePreviews(project, previewDescriptor, javaDeclaration.javaPsi)
override fun applyFix(project: Project, element: PsiElement, updater: ModPsiUpdater) {
val javaDeclaration = getUParentForIdentifier(element)?.asSafely<UMethod>() ?: return
applyFixes(project, javaDeclaration.javaPsi, element.containingFile ?: return)
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val javaDeclaration = getUParentForIdentifier(descriptor.psiElement)?.asSafely<UMethod>() ?: return
applyFixes(project, descriptor, javaDeclaration.javaPsi)
}
override fun getActions(project: Project, descriptor: ProblemDescriptor): List<(JvmModifiersOwner) -> List<IntentionAction>> {
override fun getActions(project: Project): List<(JvmModifiersOwner) -> List<IntentionAction>> {
val actions = mutableListOf<(JvmModifiersOwner) -> List<IntentionAction>>()
if (shouldBeVoidType == true) {
actions.add { jvmMethod -> createChangeTypeActions(
@@ -1209,7 +1196,7 @@ private class JUnitMalformedSignatureVisitor(
) }
}
if (newVisibility != null) {
actions.add { jvmMethod -> createModifierActions(jvmMethod, modifierRequest(newVisibility, true)) }
actions.add { jvmMethod -> createModifierActions(jvmMethod, modifierRequest(newVisibility, true, false)) }
}
if (inCorrectParams != null) {
actions.add { jvmMethod -> createChangeParametersActions(
@@ -1218,7 +1205,7 @@ private class JUnitMalformedSignatureVisitor(
) }
}
if (makeStatic != null) {
actions.add { jvmMethod -> createModifierActions(jvmMethod, modifierRequest(JvmModifier.STATIC, makeStatic)) }
actions.add { jvmMethod -> createModifierActions(jvmMethod, modifierRequest(JvmModifier.STATIC, makeStatic, false)) }
}
return actions
}
@@ -20,7 +20,8 @@ abstract class KotlinQuickFixAction<out T : PsiElement>(element: T) : QuickFixAc
final override fun invoke(project: Project, editor: Editor?, file: PsiFile) {
val element = element ?: return
if (file is KtFile && IntentionPreviewUtils.prepareElementForWrite(element)) {
if (file is KtFile &&
(startInWriteAction() && getElementToMakeWritable(file) == file || IntentionPreviewUtils.prepareElementForWrite(element))) {
invoke(project, editor, file)
}
}
@@ -15,7 +15,8 @@ abstract class KotlinCrossLanguageQuickFixAction<out T : PsiElement>(element: T)
final override fun invoke(project: Project, editor: Editor?, file: PsiFile) {
val element = element
if (element != null && IntentionPreviewUtils.prepareElementForWrite(element)) {
if (element != null &&
(startInWriteAction() && getElementToMakeWritable(file) == file || IntentionPreviewUtils.prepareElementForWrite(element))) {
invokeImpl(project, editor, file)
}
}
@@ -1,20 +1,14 @@
// 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.preview.IntentionPreviewInfo
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.base.psi.getOrCreateCompanionObject
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.intentions.AddJvmStaticIntention
import org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.util.addAnnotation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
@@ -24,61 +18,23 @@ import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
class MakeMemberStaticFix(declaration: KtNamedDeclaration) : KotlinQuickFixAction<KtNamedDeclaration>(declaration) {
override fun startInWriteAction(): Boolean = false
override fun generatePreview(project: Project, editor: Editor, file: PsiFile): IntentionPreviewInfo {
val declaration = element ?: return IntentionPreviewInfo.EMPTY
if (declaration is KtClass) {
if (declaration.hasModifier(KtTokens.INNER_KEYWORD)) declaration.removeModifier(KtTokens.INNER_KEYWORD)
return IntentionPreviewInfo.DIFF
}
val copyDeclaration = PsiTreeUtil.findSameElementInCopy(declaration, file)
val containingClass = copyDeclaration.containingClassOrObject ?: return IntentionPreviewInfo.EMPTY
val copyDeclarationInCompanion = if (containingClass is KtClass) {
val companionObject = containingClass.getOrCreateCompanionObject()
MoveMemberToCompanionObjectIntention.Factory.removeModifiers(copyDeclaration)
val newDeclaration = companionObject.addDeclaration(copyDeclaration)
copyDeclaration.delete()
newDeclaration
} else copyDeclaration
if (AddJvmStaticIntention().applicabilityRange(copyDeclarationInCompanion) != null) {
copyDeclarationInCompanion.addAnnotation(JVM_STATIC_FQ_NAME)
CodeStyleManager.getInstance(declaration.project).reformat(copyDeclarationInCompanion, true)
}
return IntentionPreviewInfo.DIFF
}
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
fun makeStaticAndReformat(declaration: KtNamedDeclaration, editor: Editor?) {
val intention = AddJvmStaticIntention()
if (intention.applicabilityRange(declaration) != null) {
intention.applyTo(declaration, editor)
runWriteAction { CodeStyleManager.getInstance(declaration.project).reformat(declaration, true) }
}
}
val declaration = element ?: return
var declaration = element ?: return
if (declaration is KtClass) {
if (declaration.hasModifier(KtTokens.INNER_KEYWORD)) declaration.removeModifier(KtTokens.INNER_KEYWORD)
return
}
val containingClass = declaration.containingClassOrObject ?: return
if (containingClass is KtClass) {
val moveMemberToCompanionObjectIntention = MoveMemberToCompanionObjectIntention()
val (conflicts, externalUsages, outerInstanceUsages) =
moveMemberToCompanionObjectIntention.retrieveConflictsAndUsages(project, editor, declaration, containingClass)
?: return
project.checkConflictsInteractively(conflicts) {
ApplicationManagerEx.getApplicationEx().runWriteActionWithNonCancellableProgressInDispatchThread(
KotlinBundle.message("making.member.static"), project, null
) {
val movedDeclaration = moveMemberToCompanionObjectIntention.doMove(
it, declaration, externalUsages, outerInstanceUsages, editor
)
makeStaticAndReformat(movedDeclaration, editor)
}
} else {
val containingClass = declaration.containingClassOrObject ?: return
if (containingClass is KtClass) {
val moveMemberToCompanionObjectIntention = MoveMemberToCompanionObjectIntention()
declaration = moveMemberToCompanionObjectIntention.doMove(
EmptyProgressIndicator(), declaration, listOf(), listOf(), editor
)
}
} else makeStaticAndReformat(declaration, editor)
}
if (AddJvmStaticIntention().applicabilityRange(declaration) != null) {
declaration.addAnnotation(JVM_STATIC_FQ_NAME)
CodeStyleManager.getInstance(project).reformat(declaration, true)
}
}
override fun getText(): String = KotlinBundle.message("make.member.static.quickfix", element?.name ?: "")