mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] IDEA-377910 Add AnnotateAsNullMarkedFix
#IDEA-377910 Fixed Merge-request: IJ-MR-177841 Merged-by: Marcin Mikosik <marcin.mikosik@jetbrains.com> (cherry picked from commit fef51fbed5f227086d864dfc6483298585a78d8f) IJ-CR-179837 GitOrigin-RevId: 8c43501fabba014db05c3a4930bcaeb69c865597
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e76cf5cd46
commit
e14522817c
@@ -96,6 +96,10 @@ inspection.i18n.quickfix.annotate=Annotate\u2026
|
||||
inspection.i18n.quickfix.annotate.as=Annotate as ''@{0}''
|
||||
inspection.i18n.quickfix.annotate.element=Annotate {0} ''{1}''\u2026
|
||||
inspection.i18n.quickfix.annotate.element.as=Annotate {0} ''{1}'' as ''@{2}''
|
||||
inspection.i18n.quickfix.annotate.container.as=Annotate container as ''@{0}''
|
||||
inspection.i18n.quickfix.annotate.choose.container=Choose Container
|
||||
inspection.i18n.quickfix.remove.annotation=Remove ''@{0}''
|
||||
inspection.i18n.quickfix.remove.annotation.from.element=Remove ''@{0}'' from {1} ''{2}''
|
||||
inspection.reference.default.package=<default>
|
||||
error.message.invalid.java.type=Invalid Java type
|
||||
move.0.to.the.beginning=Move ''{0}'' to the beginning
|
||||
|
||||
+10
-5
@@ -24,6 +24,7 @@ import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -90,11 +91,7 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement implements Lo
|
||||
final String name = PsiFormatUtil.formatSimple((PsiNamedElement)modifierListOwner);
|
||||
if (name != null) {
|
||||
JavaElementKind type = JavaElementKind.fromElement(modifierListOwner).lessDescriptive();
|
||||
if (shortName == null) {
|
||||
return JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.element", type.object(), name);
|
||||
}
|
||||
return JavaAnalysisBundle
|
||||
.message("inspection.i18n.quickfix.annotate.element.as", type.object(), name, shortName);
|
||||
return calcText(shortName, type, name);
|
||||
}
|
||||
}
|
||||
if (shortName == null) {
|
||||
@@ -103,6 +100,14 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement implements Lo
|
||||
return JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.as", shortName);
|
||||
}
|
||||
|
||||
public static @Nls @NotNull String calcText(@Nullable String shortName, @NotNull JavaElementKind type, @NotNull String name) {
|
||||
if (shortName == null) {
|
||||
return JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.element", type.object(), name);
|
||||
}
|
||||
return JavaAnalysisBundle
|
||||
.message("inspection.i18n.quickfix.annotate.element.as", type.object(), name, shortName);
|
||||
}
|
||||
|
||||
public static @Nullable PsiModifierListOwner getContainer(PsiFile file, int offset) {
|
||||
return getContainer(file, offset, false);
|
||||
}
|
||||
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.nullable;
|
||||
|
||||
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
|
||||
import com.intellij.codeInspection.nullable.MultipleElementsModCommandAction.Change;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.java.library.JavaLibraryUtil;
|
||||
import com.intellij.modcommand.*;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.util.JavaElementKind;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.psi.PsiPackage.PACKAGE_INFO_FILE;
|
||||
import static com.intellij.psi.util.PsiTreeUtil.getChildOfType;
|
||||
|
||||
|
||||
@NotNullByDefault
|
||||
public final class AnnotateAsNullMarkedFix implements ModCommandAction {
|
||||
private static final String NULL_MARKED_FQN = "org.jspecify.annotations.NullMarked";
|
||||
private static final String NULL_UNMARKED_FQN = "org.jspecify.annotations.NullUnmarked";
|
||||
private final @Nls String familyName;
|
||||
private final List<ModCommandAction> actions;
|
||||
|
||||
/**
|
||||
* Creates a quick fix for the PsiTypeElement which is highlighted as expected to be marked with not-null annotation.
|
||||
* For each container (as specified in JSpecify spec) which encloses passed typeElement
|
||||
* a quick-fix is generated that either adds @NullMarked annotation or removes @NullUnmarked annotation
|
||||
* if-and-only-if such modification solves a nullability problem (missing expected not-null annotation).
|
||||
* If no quick-fix is found, then null is returned.
|
||||
* If only one quick-fix is found, then it will be returned.
|
||||
* If multiple quick-fixes are found, then a quick-fix that displays chooser with available quick-fixes is returned.
|
||||
*
|
||||
* @param typeElement element with missing not-null annotation
|
||||
* @param nullables list of nullable annotations that should be removed from typeElement when applying any quick-fix
|
||||
*/
|
||||
public static ModCommandAction createAnnotateAsNullMarkedFix(PsiTypeElement typeElement, List<String> nullables) {
|
||||
return new AnnotateAsNullMarkedFix(provideNullMarkedFixes(typeElement, nullables));
|
||||
}
|
||||
|
||||
private AnnotateAsNullMarkedFix(List<ModCommandAction> actions) {
|
||||
this.actions = actions;
|
||||
this.familyName =
|
||||
JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.container.as", StringUtil.getShortName(NULL_MARKED_FQN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Presentation getPresentation(ActionContext context) {
|
||||
return switch (actions.size()) {
|
||||
case 0 -> null;
|
||||
case 1 -> actions.getFirst().getPresentation(context);
|
||||
default -> Presentation.of(familyName);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return familyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModCommand perform(ActionContext context) {
|
||||
return ModCommand.chooseAction(JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.choose.container"), actions);
|
||||
}
|
||||
|
||||
private static List<ModCommandAction> provideNullMarkedFixes(PsiTypeElement typeElement, List<String> nullables) {
|
||||
if (!isJSpecifyLibraryAvailable(typeElement.getContainingFile())) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<Change<? extends PsiElement>> childrenChanges = new ArrayList<>();
|
||||
addChangesThatRemoveNullableAnnotations(typeElement, nullables, childrenChanges);
|
||||
List<? extends Container<? extends PsiElement>> containers = getContainersThatCanBeAnnotatedWithNullMarked(typeElement);
|
||||
|
||||
var modCommandActions = new ArrayList<ModCommandAction>();
|
||||
for (int i = 0; i < containers.size(); i++) {
|
||||
Container<? extends PsiElement> container = containers.get(i);
|
||||
if (container.hasNullMarkedAnnotation()) {
|
||||
if (container.hasNullUnmarkedAnnotation()) {
|
||||
// jspecify spec treats case when both @NullMarked and @NullUnmarked annotations are present
|
||||
// as the case when none is present
|
||||
var text = container.textForRemoveAnnotationAction(NULL_UNMARKED_FQN);
|
||||
Change<?> removeUnmarked = container.newChangeThatRemovesAnnotation(NULL_UNMARKED_FQN);
|
||||
modCommandActions.add(new MultipleElementsModCommandAction(text, ContainerUtil.append(childrenChanges, removeUnmarked)));
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (container.hasNullUnmarkedAnnotation()) {
|
||||
Change<?> removeUnmarked = container.newChangeThatRemovesAnnotation(NULL_UNMARKED_FQN);
|
||||
String enclosing = annotationFromNearestAnnotatedParentContainer(containers, i);
|
||||
if (enclosing != null && enclosing.equals(NULL_MARKED_FQN)) {
|
||||
var text = container.textForRemoveAnnotationAction(NULL_UNMARKED_FQN);
|
||||
modCommandActions.add(new MultipleElementsModCommandAction(text, ContainerUtil.append(childrenChanges, removeUnmarked)));
|
||||
break;
|
||||
}
|
||||
else {
|
||||
var text = container.textForAddAnnotationAction(NULL_MARKED_FQN);
|
||||
Change<?> addMarked = container.newChangeThatAddsAnnotation(NULL_MARKED_FQN);
|
||||
var changes = ContainerUtil.append(childrenChanges, removeUnmarked, addMarked);
|
||||
modCommandActions.add(new MultipleElementsModCommandAction(text, changes));
|
||||
}
|
||||
childrenChanges = ContainerUtil.append(childrenChanges, removeUnmarked);
|
||||
}
|
||||
else {
|
||||
var text = container.textForAddAnnotationAction(NULL_MARKED_FQN);
|
||||
Change<?> addMarked = container.newChangeThatAddsAnnotation(NULL_MARKED_FQN);
|
||||
modCommandActions.add(new MultipleElementsModCommandAction(text, ContainerUtil.append(childrenChanges, addMarked)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return modCommandActions;
|
||||
}
|
||||
|
||||
private static boolean isJSpecifyLibraryAvailable(PsiFile file) {
|
||||
Module module = ModuleUtilCore.findModuleForFile(file);
|
||||
if (module == null) return false;
|
||||
return JavaLibraryUtil.hasLibraryClass(module, NULL_MARKED_FQN) && JavaLibraryUtil.hasLibraryClass(module, NULL_UNMARKED_FQN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return effective annotation on the nearest enclosing container that affects container at index i.
|
||||
*/
|
||||
private static @Nullable String annotationFromNearestAnnotatedParentContainer(List<? extends Container<? extends PsiElement>> containers,
|
||||
int i) {
|
||||
for (int j = i + 1; j < containers.size(); j++) {
|
||||
Container<? extends PsiElement> container = containers.get(j);
|
||||
String annotation = container.getEffectiveAnnotation();
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<? extends Container<? extends PsiElement>> getContainersThatCanBeAnnotatedWithNullMarked(PsiElement element) {
|
||||
List<Container<? extends PsiElement>> result = new ArrayList<>();
|
||||
PsiModifierListOwner parent = PsiTreeUtil.getParentOfType(element, PsiModifierListOwner.class, true);
|
||||
while (parent != null) {
|
||||
if (parent.getModifierList() != null && (parent instanceof PsiClass || parent instanceof PsiMethod)) {
|
||||
result.add(new ModifierListOwnerContainer(parent, parent.getModifierList()));
|
||||
}
|
||||
parent = PsiTreeUtil.getParentOfType(parent, PsiModifierListOwner.class, true);
|
||||
}
|
||||
|
||||
var containingFile = element.getContainingFile();
|
||||
if (containingFile == null) return result;
|
||||
var directory = containingFile.getContainingDirectory();
|
||||
if (directory == null) return result;
|
||||
var packageStatement = getChildOfType(containingFile, PsiPackageStatement.class);
|
||||
if (packageStatement == null) return result;
|
||||
var packageFqn = packageStatement.getPackageName();
|
||||
result.add(new PackageContainer(directory, packageFqn));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addChangesThatRemoveNullableAnnotations(PsiTypeElement typeElement, List<String> nullables,
|
||||
List<Change<? extends PsiElement>> changes) {
|
||||
var annotationFqn = ContainerUtil.filter(nullables, annotation -> typeElement.hasAnnotation(annotation));
|
||||
changes.addAll(ContainerUtil.map(annotationFqn, a -> new Change<>(typeElement, (updater, element) -> removeAnnotation(element, a))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Container (PsiElement) on which JSpecify container annotation (NullMarked/NullUnmarked) can be added or removed.
|
||||
*/
|
||||
private static sealed abstract class Container<T extends PsiElement> permits PackageContainer, ModifierListOwnerContainer {
|
||||
private final boolean hasNullMarkedAnnotation;
|
||||
private final boolean hasNullUnmarkedAnnotation;
|
||||
|
||||
private Container(@Nullable PsiModifierList modifierList) {
|
||||
this.hasNullMarkedAnnotation = hasAnnotation(modifierList, NULL_MARKED_FQN);
|
||||
this.hasNullUnmarkedAnnotation = hasAnnotation(modifierList, NULL_UNMARKED_FQN);
|
||||
}
|
||||
|
||||
private static boolean hasAnnotation(@Nullable PsiModifierList modifierList, String annotationFqn) {
|
||||
return modifierList != null && modifierList.hasAnnotation(annotationFqn);
|
||||
}
|
||||
|
||||
private boolean hasNullMarkedAnnotation() {
|
||||
return hasNullMarkedAnnotation;
|
||||
}
|
||||
|
||||
private boolean hasNullUnmarkedAnnotation() {
|
||||
return hasNullUnmarkedAnnotation;
|
||||
}
|
||||
|
||||
public @Nullable String getEffectiveAnnotation() {
|
||||
// jspecify spec treats the case when both annotations are present as the case when none is present
|
||||
if (hasNullMarkedAnnotation && hasNullUnmarkedAnnotation) return null;
|
||||
if (hasNullMarkedAnnotation) return NULL_MARKED_FQN;
|
||||
if (hasNullUnmarkedAnnotation) return NULL_UNMARKED_FQN;
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract Change<T> newChangeThatAddsAnnotation(String annotationFqn);
|
||||
|
||||
public abstract Change<T> newChangeThatRemovesAnnotation(String annotationFqn);
|
||||
|
||||
@IntentionName
|
||||
public abstract String textForAddAnnotationAction(String annotationFqn);
|
||||
|
||||
@IntentionName
|
||||
public abstract String textForRemoveAnnotationAction(String annotationFqn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Container operations for PsiModifierListOwner.
|
||||
* Add/Remove annotation operations are applied to modifier list.
|
||||
*/
|
||||
private static final class ModifierListOwnerContainer extends Container<PsiModifierList> {
|
||||
private final PsiModifierListOwner modifierListOwner;
|
||||
private final PsiModifierList modifierList;
|
||||
|
||||
private ModifierListOwnerContainer(PsiModifierListOwner modifierListOwner, PsiModifierList modifierList) {
|
||||
super(modifierList);
|
||||
this.modifierListOwner = modifierListOwner;
|
||||
this.modifierList = modifierList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change<PsiModifierList> newChangeThatAddsAnnotation(String annotationFqn) {
|
||||
return new Change<>(modifierList, (updater, m) -> updater.highlight(
|
||||
JavaCodeStyleManager.getInstance(m.getProject()).shortenClassReferences(m.addAnnotation(annotationFqn))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change<PsiModifierList> newChangeThatRemovesAnnotation(String annotationFqn) {
|
||||
return new Change<>(modifierList, (updater, modifierList) -> removeAnnotation(modifierList, annotationFqn));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textForAddAnnotationAction(String annotationFqn) {
|
||||
return AddAnnotationPsiFix.calcText(modifierListOwner, annotationFqn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textForRemoveAnnotationAction(String annotationFqn) {
|
||||
if (modifierListOwner instanceof PsiNamedElement) {
|
||||
String name = PsiFormatUtil.formatSimple((PsiNamedElement)modifierListOwner);
|
||||
if (name != null) {
|
||||
var javaElementKind = JavaElementKind.fromElement(modifierListOwner).lessDescriptive();
|
||||
return createTextForRemoveAnnotationAction(annotationFqn, name, javaElementKind);
|
||||
}
|
||||
}
|
||||
return JavaAnalysisBundle.message("inspection.i18n.quickfix.remove.annotation", StringUtil.getShortName(annotationFqn));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides container operations for a package.
|
||||
* Remove/Add operations are applied to package statement in package-info.java file
|
||||
* which is created on the fly if it doesn't exist.
|
||||
*/
|
||||
private static final class PackageContainer extends Container<PsiDirectory> {
|
||||
private final PsiDirectory directory;
|
||||
private final String packageFqn;
|
||||
|
||||
private PackageContainer(PsiDirectory directory, String packageFqn) {
|
||||
super(getPackageInfoModifierList(directory));
|
||||
this.directory = directory;
|
||||
this.packageFqn = packageFqn;
|
||||
}
|
||||
|
||||
private static @Nullable PsiModifierList getPackageInfoModifierList(PsiDirectory directory) {
|
||||
directory.findFile(PACKAGE_INFO_FILE);
|
||||
var packageInfoFile = directory.findFile(PACKAGE_INFO_FILE);
|
||||
if (packageInfoFile == null) return null;
|
||||
if (!(packageInfoFile instanceof PsiJavaFile packageInfoJavaFile)) return null;
|
||||
var packageStatement = packageInfoJavaFile.getPackageStatement();
|
||||
if (packageStatement == null) return null;
|
||||
return packageStatement.getAnnotationList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change<PsiDirectory> newChangeThatAddsAnnotation(String annotationFqn) {
|
||||
return new Change<>(directory, (updater, dir) -> {
|
||||
var packageInfoFile = updater.getWritable(getPackageInfoFile(updater, dir));
|
||||
var packageStatement = getPackageStatement(packageInfoFile, dir.getProject());
|
||||
var elementFactory = JavaPsiFacade.getElementFactory(dir.getProject());
|
||||
var annotation = elementFactory.createAnnotationFromText("@" + annotationFqn, packageStatement);
|
||||
var addedAnnotation = packageInfoFile.addBefore(annotation, packageStatement);
|
||||
JavaCodeStyleManager.getInstance(dir.getProject()).shortenClassReferences(addedAnnotation);
|
||||
CodeStyleManager.getInstance(directory.getProject()).reformat(packageInfoFile);
|
||||
updater.moveCaretTo(packageStatement);
|
||||
updater.highlight(addedAnnotation);
|
||||
});
|
||||
}
|
||||
|
||||
private PsiPackageStatement getPackageStatement(PsiFile packageInfoFile, Project project) {
|
||||
if (packageInfoFile instanceof PsiJavaFile packageInfoJavaFile) {
|
||||
var packageStatement = packageInfoJavaFile.getPackageStatement();
|
||||
if (packageStatement != null) return packageStatement;
|
||||
}
|
||||
var elementFactory = JavaPsiFacade.getElementFactory(project);
|
||||
var packageStatement = elementFactory.createPackageStatement(packageFqn);
|
||||
return (PsiPackageStatement)packageInfoFile.addBefore(packageStatement, packageInfoFile.getFirstChild());
|
||||
}
|
||||
|
||||
private static PsiFile getPackageInfoFile(ModPsiUpdater updater, PsiDirectory directory) {
|
||||
var packageInfoFile = directory.findFile(PACKAGE_INFO_FILE);
|
||||
if (packageInfoFile == null) {
|
||||
packageInfoFile = updater.getWritable(directory).createFile(PACKAGE_INFO_FILE);
|
||||
}
|
||||
return packageInfoFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change<PsiDirectory> newChangeThatRemovesAnnotation(String annotationFqn) {
|
||||
return new Change<>(directory, (updater, dir) -> {
|
||||
var packageInfoFile = updater.getWritable(getPackageInfoFile(updater, dir));
|
||||
var packageStatement = getPackageStatement(packageInfoFile, dir.getProject());
|
||||
var modifierList = packageStatement.getAnnotationList();
|
||||
if (modifierList == null) return;
|
||||
removeAnnotation(modifierList, annotationFqn);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textForAddAnnotationAction(String annotationFqn) {
|
||||
return AddAnnotationPsiFix.calcText(StringUtil.getShortName(annotationFqn), JavaElementKind.PACKAGE, packageFqn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textForRemoveAnnotationAction(String annotationFqn) {
|
||||
return createTextForRemoveAnnotationAction(annotationFqn, packageFqn, JavaElementKind.PACKAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeAnnotation(PsiAnnotationOwner annotationOwner, String annotationFqn) {
|
||||
var annotation = annotationOwner.findAnnotation(annotationFqn);
|
||||
if (annotation != null) {
|
||||
annotation.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nls String createTextForRemoveAnnotationAction(String annotationFqn, String name, JavaElementKind elementKind) {
|
||||
String shortName = StringUtil.getShortName(annotationFqn);
|
||||
return JavaAnalysisBundle.message("inspection.i18n.quickfix.remove.annotation.from.element", shortName, elementKind.object(), name);
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.nullable;
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.modcommand.*;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* A ModCommand action that performs multiple PsiElement changes.
|
||||
* Each change is passed a a {@link Change} object
|
||||
* that contains PsiElement to be modified (target) and the operation to performed.
|
||||
* Internally this action uses {@link SmartPsiElementPointer}s to track the target elements.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class MultipleElementsModCommandAction implements ModCommandAction {
|
||||
@IntentionName
|
||||
private final String familyName;
|
||||
private final List<SmartChange<? extends PsiElement>> changes;
|
||||
|
||||
/**
|
||||
* Creates a new action that performs multiple PsiElement changes.
|
||||
*
|
||||
* @param familyName the name of the action family to be displayed in the UI
|
||||
* @param changes list of changes to be performed on PsiElements
|
||||
*/
|
||||
MultipleElementsModCommandAction(@IntentionName String familyName, List<Change<? extends PsiElement>> changes) {
|
||||
this.changes = ContainerUtil.map(changes, change -> change.toSmartChange());
|
||||
this.familyName = familyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return familyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Presentation getPresentation(ActionContext context) {
|
||||
return Presentation.of(familyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModCommand perform(ActionContext context) {
|
||||
return ModCommand.psiUpdate(context, updater -> applyOperations(changes, updater));
|
||||
}
|
||||
|
||||
private static <T extends PsiElement> void applyOperations(List<SmartChange<? extends T>> smartChanges, ModPsiUpdater updater) {
|
||||
List<? extends Change<? extends T>> changes = convertSmartChangesToChanges(updater, smartChanges);
|
||||
if (changes == null) return;
|
||||
for (Change<? extends T> change : changes) {
|
||||
change.invokeOperation(updater);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends PsiElement> @Nullable List<? extends Change<? extends T>> convertSmartChangesToChanges(ModPsiUpdater updater,
|
||||
List<SmartChange<? extends T>> smartChanges) {
|
||||
List<Change<? extends T>> changes = new ArrayList<>();
|
||||
for (SmartChange<? extends T> smartChange : smartChanges) {
|
||||
Change<? extends T> change = smartChange.toChange(updater);
|
||||
if (change == null) return null;
|
||||
changes.add(change);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a change operation to be performed on a PsiElement.
|
||||
*
|
||||
* @param target the PsiElement to be modified
|
||||
* @param operation the operation to be performed on the target element
|
||||
* @param <T> the type of PsiElement
|
||||
*/
|
||||
public record Change<T extends PsiElement>(T target, BiConsumer<ModPsiUpdater, T> operation) {
|
||||
SmartChange<T> toSmartChange() {
|
||||
return new SmartChange<>(SmartPointerManager.createPointer(target), operation);
|
||||
}
|
||||
|
||||
public void invokeOperation(ModPsiUpdater updater) {
|
||||
operation().accept(updater, target());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a change operation to be performed on a PsiElement tracked by SmartPsiElementPointer.
|
||||
*
|
||||
* @param target smart pointer to the PsiElement to be modified
|
||||
* @param operation the operation to be performed on the target element
|
||||
* @param <T> the type of PsiElement
|
||||
*/
|
||||
private record SmartChange<T extends PsiElement>(SmartPsiElementPointer<T> target, BiConsumer<ModPsiUpdater, T> operation) {
|
||||
private @Nullable Change<T> toChange(ModPsiUpdater updater) {
|
||||
T writable = updater.getWritable(target.getElement());
|
||||
if (writable == null) return null;
|
||||
return new Change<>(writable, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -48,6 +48,7 @@ import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.intellij.codeInsight.AnnotationUtil.*;
|
||||
import static com.intellij.codeInspection.nullable.AnnotateAsNullMarkedFix.createAnnotateAsNullMarkedFix;
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
@@ -442,12 +443,13 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
!(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType wildcardType && !wildcardType.isExtends())) {
|
||||
String annotationToAdd = manager.getDefaultAnnotation(Nullability.NOT_NULL, reference);
|
||||
PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationToAdd, element.getResolveScope());
|
||||
AddTypeAnnotationFix fix = null;
|
||||
List<LocalQuickFix> fixes = new ArrayList<>();
|
||||
if (annotationClass != null &&
|
||||
AnnotationTargetUtil.findAnnotationTarget(annotationClass, PsiAnnotation.TargetType.TYPE_USE) != null) {
|
||||
fix = new AddTypeAnnotationFix(typeArgument, annotationToAdd, manager.getNullables());
|
||||
fixes.add(LocalQuickFix.from(new AddTypeAnnotationFix(typeArgument, annotationToAdd, manager.getNullables())));
|
||||
}
|
||||
reportProblem(holder, typeArgument, fix, "non.null.type.argument.is.expected");
|
||||
fixes.add(LocalQuickFix.from(createAnnotateAsNullMarkedFix(typeArgument, manager.getNullables()), false));
|
||||
reportProblem(holder, typeArgument, fixes.toArray(LocalQuickFix.EMPTY_ARRAY), "non.null.type.argument.is.expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
class InnerEnclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate method 'foo()' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
@NullMarked
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
class Inner {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate method 'foo()' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
@NullMarked
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Annotate container as '@NullMarked'|->Remove '@NullUnmarked' from class 'Enclosing'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Outer {
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Annotate container as '@NullMarked'|->Remove '@NullUnmarked' from class 'Enclosing'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<@org.jetbrains.annotations.Nullable <caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
class InnerEnclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate method 'foo()' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
@org.jspecify.annotations.NullUnmarked
|
||||
class Inner {
|
||||
@org.jspecify.annotations.NullUnmarked
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate method 'foo()' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
@org.jspecify.annotations.NullUnmarked
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Annotate container as '@NullMarked'|->Annotate class 'Enclosing' as '@NullMarked'" "true"
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
class Enclosing {
|
||||
@org.jspecify.annotations.NullUnmarked
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Annotate container as '@NullMarked'|->Remove '@NullUnmarked' from class 'Enclosing'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullMarked
|
||||
class Outer {
|
||||
@NullUnmarked
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Annotate container as '@NullMarked'|->Remove '@NullUnmarked' from class 'Enclosing'" "true"
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
@NullUnmarked
|
||||
@NullMarked
|
||||
class Enclosing {
|
||||
public void foo(NonNullHolder<<caret>String> holder) {
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInspection.nullable.NullableStuffInspection;
|
||||
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ContentEntry;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.testFramework.fixtures.MavenDependencyUtil;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class AnnotateAsNullMarkedFixPackageTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
@Language("JAVA")
|
||||
private static final String ENCLOSING_CLASS_FILE_TEXT = """
|
||||
package com.example;
|
||||
|
||||
class NonNullHolder<T extends @org.jspecify.annotations.NonNull Object> {}
|
||||
|
||||
public class Enclosing {
|
||||
public void foo(NonNullHolder< <caret>String> holder) {
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return new DefaultLightProjectDescriptor() {
|
||||
@Override
|
||||
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
|
||||
MavenDependencyUtil.addFromMaven(model, "org.jspecify:jspecify:1.0.0");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testAnnotatePackageAsNullMarkedWhenPackageInfoIsMissing() {
|
||||
configureJavaFileFromPathAndText("com/example/Enclosing.java", ENCLOSING_CLASS_FILE_TEXT);
|
||||
|
||||
runAnnotatePackageAsNullMarkedAction();
|
||||
|
||||
assertJavaFileContent("com/example/package-info.java", """
|
||||
@NullMarked
|
||||
package com.example;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;""");
|
||||
}
|
||||
|
||||
public void testAnnotatePackageAsNullMarkedWhenPackageInfoIsPresent() {
|
||||
configureJavaFileFromPathAndText("com/example/Enclosing.java", ENCLOSING_CLASS_FILE_TEXT);
|
||||
myFixture.addFileToProject("com/example/package-info.java", "package com.example;");
|
||||
|
||||
runAnnotatePackageAsNullMarkedAction();
|
||||
|
||||
assertJavaFileContent("com/example/package-info.java", """
|
||||
@NullMarked
|
||||
package com.example;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;""");
|
||||
}
|
||||
|
||||
public void testAnnotatePackageAsNullMarkedWhenPackageInfoIsPresentAndContainsNullUnmarkedAnnotation() {
|
||||
configureJavaFileFromPathAndText("com/example/Enclosing.java", ENCLOSING_CLASS_FILE_TEXT);
|
||||
myFixture.addFileToProject("com/example/package-info.java", """
|
||||
@NullUnmarked
|
||||
package com.example;
|
||||
|
||||
import org.jspecify.annotations.NullUnmarked;""");
|
||||
|
||||
runAnnotatePackageAsNullMarkedAction();
|
||||
|
||||
assertJavaFileContent("com/example/package-info.java", """
|
||||
@NullMarked
|
||||
package com.example;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.NullUnmarked;""");
|
||||
}
|
||||
|
||||
private void runAnnotatePackageAsNullMarkedAction() {
|
||||
myFixture.enableInspections(new NullableStuffInspection());
|
||||
var intentionAction = myFixture.findSingleIntention("Annotate container as '@NullMarked'");
|
||||
UiInterceptors.register(new ChooserInterceptor(null, Pattern.quote("Annotate package 'com.example' as '@NullMarked'")));
|
||||
myFixture.launchAction(intentionAction);
|
||||
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
|
||||
}
|
||||
|
||||
private void configureJavaFileFromPathAndText(String path, @Language("JAVA") String text) {
|
||||
VirtualFile file = myFixture.addFileToProject(path, text).getVirtualFile();
|
||||
myFixture.configureFromExistingVirtualFile(file);
|
||||
}
|
||||
|
||||
private void assertJavaFileContent(String path, @Language("JAVA") String expected) {
|
||||
myFixture.checkResult(path, expected, false);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.nullable.NullableStuffInspection;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ContentEntry;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.MavenDependencyUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AnnotateAsNullMarkedFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/annotateAsNullMarkedFix";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return new SimpleLightProjectDescriptor(getModuleTypeId(), getProjectJDK()) {
|
||||
@Override
|
||||
protected void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
|
||||
DefaultLightProjectDescriptor.addJetBrainsAnnotationsWithTypeUse(model);
|
||||
MavenDependencyUtil.addFromMaven(model, "org.jspecify:jspecify:1.0.0");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new NullableStuffInspection()};
|
||||
}
|
||||
}
|
||||
@@ -299,6 +299,7 @@ com.intellij.codeInspection.LocalQuickFix
|
||||
- sf:EMPTY_ARRAY:com.intellij.codeInspection.LocalQuickFix[]
|
||||
- availableInBatchMode():Z
|
||||
- s:from(com.intellij.modcommand.ModCommandAction):com.intellij.codeInspection.LocalQuickFix
|
||||
- s:from(com.intellij.modcommand.ModCommandAction,Z):com.intellij.codeInspection.LocalQuickFix
|
||||
- generatePreview(com.intellij.openapi.project.Project,com.intellij.codeInspection.ProblemDescriptor):com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
|
||||
- getRangesToHighlight(com.intellij.openapi.project.Project,com.intellij.codeInspection.ProblemDescriptor):java.util.List
|
||||
- s:notNullElements(com.intellij.codeInspection.LocalQuickFix):com.intellij.codeInspection.LocalQuickFix[]
|
||||
@@ -637,6 +638,7 @@ com.intellij.modcommand.ModCommandService
|
||||
- a:wrap(com.intellij.modcommand.ModCommandAction):com.intellij.codeInsight.intention.IntentionAction
|
||||
- a:wrapToLocalQuickFixAndIntentionActionOnPsiElement(com.intellij.modcommand.ModCommandAction,com.intellij.psi.PsiElement):com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
|
||||
- a:wrapToQuickFix(com.intellij.modcommand.ModCommandAction):com.intellij.codeInspection.LocalQuickFix
|
||||
- a:wrapToQuickFix(com.intellij.modcommand.ModCommandAction,Z):com.intellij.codeInspection.LocalQuickFix
|
||||
com.intellij.modcommand.ModPsiUpdater
|
||||
- com.intellij.modcommand.ModPsiNavigator
|
||||
- a:cancel(java.lang.String):V
|
||||
|
||||
@@ -124,4 +124,9 @@ public interface LocalQuickFix extends QuickFix<ProblemDescriptor>, FileModifier
|
||||
static @Nullable LocalQuickFix from(@Nullable ModCommandAction action) {
|
||||
return action == null ? null : ModCommandService.getInstance().wrapToQuickFix(action);
|
||||
}
|
||||
|
||||
@Contract("null, _ -> null; !null, _ -> !null")
|
||||
static @Nullable LocalQuickFix from(@Nullable ModCommandAction action, boolean availableInBatchMode) {
|
||||
return action == null ? null : ModCommandService.getInstance().wrapToQuickFix(action, availableInBatchMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ public interface ModCommandService {
|
||||
*/
|
||||
@NotNull LocalQuickFix wrapToQuickFix(@NotNull ModCommandAction action);
|
||||
|
||||
@NotNull LocalQuickFix wrapToQuickFix(@NotNull ModCommandAction action, boolean availableInBatchMode);
|
||||
|
||||
/**
|
||||
* @param fix {@link LocalQuickFix}
|
||||
* @return a {@link ModCommandAction} which is wrapped inside the supplied quick-fix; null if the supplied quick-fix
|
||||
|
||||
+11
@@ -13,9 +13,20 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
final class ModCommandActionQuickFixWrapper extends ModCommandQuickFix implements ReportingClassSubstitutor {
|
||||
private final ModCommandAction myAction;
|
||||
private boolean myAvailableInBatchMode;
|
||||
|
||||
ModCommandActionQuickFixWrapper(@NotNull ModCommandAction action) {
|
||||
this(action, true);
|
||||
}
|
||||
|
||||
ModCommandActionQuickFixWrapper(@NotNull ModCommandAction action, boolean availableInBatchMode) {
|
||||
myAction = action;
|
||||
myAvailableInBatchMode = availableInBatchMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean availableInBatchMode() {
|
||||
return myAvailableInBatchMode;
|
||||
}
|
||||
|
||||
ModCommandAction getAction() {
|
||||
|
||||
+6
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ApiStatus.Internal
|
||||
@@ -44,6 +45,11 @@ public final class ModCommandServiceImpl implements ModCommandService {
|
||||
return new ModCommandActionQuickFixWrapper(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LocalQuickFix wrapToQuickFix(@NotNull ModCommandAction action, boolean availableInBatchMode) {
|
||||
return new ModCommandActionQuickFixWrapper(action, availableInBatchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ModCommandAction unwrap(@NotNull LocalQuickFix fix) {
|
||||
if (fix instanceof ModCommandActionQuickFixWrapper wrapper) {
|
||||
|
||||
Reference in New Issue
Block a user