From ac013e30b2de0a059fb56a8c714635116996ca7a Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 16 Jul 2025 15:41:15 +0200 Subject: [PATCH] [java] Make default annotations context-dependent Default annotation is now defined from PSI context using an ordered list of nullable or notnull annotations. The first available annotation is selected. For Spring Boot 3, default annotation is org.springframework.lang.NonNull/Nullable For Spring Boot 4, default annotation is org.jspecify.annotations.NonNull/Nullable Legacy settings with an unordered annotation list are converted to make them ordered Non-context-specific default annotations are still used as a default for external and inferred annotation; also it's a fallback option in contexts where no annotation is available at all ChangeNullableDefaultsFix is removed, as now it's unlikely to produce the desired result (and probably was not useful before as well) Fixes IDEA-372145 Support for third-party nullability providers GitOrigin-RevId: 31794eb024ba310ca35d4b3df33420d0bb344898 --- .../settings/CaptureConfigurable.java | 10 +- .../messages/JavaAnalysisBundle.properties | 4 +- .../resources/META-INF/JavaAnalysisPlugin.xml | 8 +- .../AddAnnotationModCommandAction.java | 5 +- .../impl/AddNotNullAnnotationFix.java | 3 +- .../impl/AddNullableAnnotationFix.java | 3 +- .../dataFlow/DataFlowInspectionBase.java | 32 +- .../nullable/ChangeNullableDefaultsFix.java | 55 --- .../nullable/NullableStuffInspectionBase.java | 83 ++--- .../AssignmentToNullInspection.java | 3 +- .../extractMethod/ExtractMethodDialog.java | 4 +- .../extractMethod/ExtractMethodProcessor.java | 9 +- .../newImpl/ExtractMethodHelper.kt | 12 +- .../ExtractMethodObjectProcessor.java | 3 +- .../IntroduceParameterHandler.java | 2 + .../MakeInferredAnnotationExplicit.java | 17 +- .../NullableNotNullManagerImpl.java | 76 ++++- .../AnnotationPackageSupport.java | 10 +- .../JavaClassNameInsertHandler.java | 3 +- ...rrideImplementsAnnotationsHandlerImpl.java | 9 +- .../inferNullity/AnnotateTask.java | 2 +- .../inferNullity/NullityInferrer.java | 10 +- .../siyeh/ig/bugs/ReturnNullInspection.java | 10 +- .../intellij/codeInsight/AnnotationUtil.java | 4 - .../codeInsight/NullableNotNullManager.java | 35 +- .../afterOverriddenWithInferred.java | 2 +- .../beforeOverriddenWithInferred.java | 2 +- .../InterfaceMethodVisibility_after.java | 3 + .../InterfaceMethodVisibility_after.java | 3 + .../AnnotateMethodInGeneratedFilesTest.java | 2 +- .../impl/quickfix/AnnotateMethodTest.java | 18 +- .../MakeInferredAnnotationExplicitTest.java | 5 - .../codeInsight/OverrideImplement15Test.java | 12 +- .../DataFlowInspection8Test.java | 16 - .../NullableStuffInspectionTest.java | 24 +- .../resources/messages/JavaBundle.properties | 2 + .../codeInsight/AnnotationsPanel.java | 196 +---------- .../codeInsight/NullableAnnotationsPanel.java | 319 ++++++++++++++++++ .../codeInsight/NullableNotNullDialog.java | 12 +- .../intellij/coverage/JavaCoverageOptions.kt | 5 +- 40 files changed, 573 insertions(+), 460 deletions(-) delete mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java create mode 100644 java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java index f078182b5de4..a44496b25551 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java @@ -514,16 +514,14 @@ public final class CaptureConfigurable implements SearchableConfigurable, NoScro mySettings = DebuggerProjectSettings.getInstance(myProject); myAsyncSchedulePanel = new AnnotationsPanel(project, JavaDebuggerBundle.message("settings.async.schedule"), - "", getAsyncAnnotations(mySettings, true), - Collections.singletonList(getAnnotationName(true)), - Collections.emptySet(), false, false); + Collections.singletonList(getAnnotationName(true)) + ); myAsyncExecutePanel = new AnnotationsPanel(project, JavaDebuggerBundle.message("settings.async.execute"), - "", getAsyncAnnotations(mySettings, false), - Collections.singletonList(getAnnotationName(false)), - Collections.emptySet(), false, false); + Collections.singletonList(getAnnotationName(false)) + ); init(); setTitle(JavaDebuggerBundle.message("settings.async.annotations.configuration")); } diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index b03920ca9acc..91c0d5dc90c1 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -157,7 +157,7 @@ inspection.annotate.overridden.method.and.self.quickfix.family.name=Annotate ove inspection.annotate.overridden.method.and.self.quickfix.name=Annotate overriding methods and self with ''@{0}'' inspection.annotate.overridden.method.quickfix.family.name=Annotate overriding methods inspection.annotate.overridden.method.quickfix.name=Annotate overriding methods with ''@{0}'' -inspection.annotate.overridden.method.nullable.quickfix.name=Annotate overriding methods as ''@{0}'' +inspection.annotate.overridden.method.nullable.quickfix.name=Add missing nullability annotation to overriding methods inspection.c.style.array.declarations.option=Ignore C-style declarations in variables inspection.can.be.final.accept.quickfix=Make final inspection.can.be.final.option1=Report methods @@ -310,7 +310,6 @@ inspection.visibility.package.private.top.level.classes=Suggest package-private inspection.visibility.private.inner.members=Suggest 'private' for inner class members when referenced from outer class only instance.member.guarded.by.static.0.loc=Instance member guarded by static "{0}" #loc instance.member.guarded.by.static.ref.loc=Instance member guarded by static #ref #loc -make.0.default.annotation=Make "{0}" default annotation make.default.the.last.case.family.name=Make 'default' the last case make.final.and.annotate.as.safevarargs=Make final and annotate as '@SafeVarargs' method.reference.mapped.to.comparator=Method reference mapped to 'Comparator' interface does not fulfill the 'Comparator' contract @@ -565,7 +564,6 @@ mutation.signature.problem.invalid.token=Invalid token: {0}; supported are ''thi required.type=Required type provided.type=Provided type.mismatch.reason=reason: {0} -inspection.message.code.generation.different.nullability.annotation.will.be.used=The generated code will use ''@{1}'' instead of ''@{0}'' change.visibility.level=Make {0} {1} inspection.redundant.tostring.option.notnull.qualifier=Report only when qualifier is known to be not-null highlighting.try.blocks=Highlighting try blocks\u2026 diff --git a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml index 153ac86d44dd..44c2aee521fa 100644 --- a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml @@ -337,14 +337,14 @@ description="Assume any implementation of methods like Object.toString() or Iterable.iterator() to be pure during bytecode inference. This assumption might lead to false-positives in some inspections, though it's believed to uncover more bugs."/> + - - - - + + + fixes = createNPEFixes(expression, top, reporter.isOnTheFly(), alwaysNull); fixes.add(LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(target))); reporter.registerProblem(expression, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); @@ -806,16 +804,18 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec } reporter.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); } - else if (AnnotationUtil.isAnnotatingApplicable(anchor)) { - final String defaultNullable = manager.getDefaultNullable(); - final String presentableNullable = StringUtil.getShortName(defaultNullable); - final String text = exactlyNull - ? JavaAnalysisBundle.message("dataflow.message.return.null.from.notnullable", presentableNullable) - : JavaAnalysisBundle.message("dataflow.message.return.nullable.from.notnullable", presentableNullable); - PsiMethod surroundingMethod = PsiTreeUtil.getParentOfType(anchor, PsiMethod.class, true, PsiLambdaExpression.class); - final LocalQuickFix fix = surroundingMethod == null ? null : - LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(surroundingMethod)); - reporter.registerProblem(expr, text, LocalQuickFix.notNullElements(fix)); + else { + final String defaultNullable = manager.getDefaultAnnotation(Nullability.NULLABLE, anchor); + if (AnnotationUtil.isAnnotatingApplicable(anchor, defaultNullable)) { + final String presentableNullable = StringUtil.getShortName(defaultNullable); + final String text = exactlyNull + ? JavaAnalysisBundle.message("dataflow.message.return.null.from.notnullable", presentableNullable) + : JavaAnalysisBundle.message("dataflow.message.return.nullable.from.notnullable", presentableNullable); + PsiMethod surroundingMethod = PsiTreeUtil.getParentOfType(anchor, PsiMethod.class, true, PsiLambdaExpression.class); + final LocalQuickFix fix = surroundingMethod == null ? null : + LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(surroundingMethod)); + reporter.registerProblem(expr, text, LocalQuickFix.notNullElements(fix)); + } } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java deleted file mode 100644 index b3e99d699491..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2000-2024 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.ProblemDescriptor; -import com.intellij.java.analysis.JavaAnalysisBundle; -import com.intellij.modcommand.ModCommand; -import com.intellij.modcommand.ModCommandQuickFix; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiAnnotation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import static com.intellij.modcommand.ModCommand.*; - -class ChangeNullableDefaultsFix extends ModCommandQuickFix { - private final String myNotNullName; - private final String myNullableName; - - ChangeNullableDefaultsFix(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { - myNotNullName = notNull != null ? notNull.getQualifiedName() : null; - myNullableName = nullable != null ? nullable.getQualifiedName() : null; - } - - ChangeNullableDefaultsFix(String notNull, String nullable) { - myNotNullName = notNull; - myNullableName = nullable; - } - - @Override - public @NotNull String getFamilyName() { - return JavaAnalysisBundle.message("make.0.default.annotation", myNotNullName != null ? myNotNullName : myNullableName); - } - - @Override - public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - ModCommand command = nop(); - if (myNotNullName != null) { - command = command.andThen( - updateOptionList(descriptor.getPsiElement(), "NullableNotNullManager.myNotNulls", list -> { - if (!list.contains(myNotNullName)) { - list.add(myNotNullName); - } - })).andThen(updateOption(descriptor.getPsiElement(), "NullableNotNullManager.myDefaultNotNull", myNotNullName)); - } - else { - command = command.andThen( - updateOptionList(descriptor.getPsiElement(), "NullableNotNullManager.myNullables", list -> { - if (!list.contains(myNullableName)) { - list.add(myNullableName); - } - })).andThen(updateOption(descriptor.getPsiElement(), "NullableNotNullManager.myDefaultNullable", myNullableName)); - } - return command; - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java index 145e182e7272..5123c1214544 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java @@ -33,7 +33,6 @@ import com.intellij.psi.impl.search.JavaOverridingMethodsSearcher; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.util.*; -import com.intellij.psi.util.ClassUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtilRt; import com.intellij.util.containers.ContainerUtil; @@ -153,7 +152,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection if (classType.getPsiContext() instanceof PsiJavaCodeReferenceElement typeRef && typeRef.getParent() instanceof PsiTypeElement typeElement && typeElement.getType().equals(classType) && typeElement.acceptsAnnotations()) { - fix = new AddTypeAnnotationFix(typeElement, manager.getDefaultNullable(), notNulls); + fix = new AddTypeAnnotationFix(typeElement, manager.getDefaultAnnotation(Nullability.NULLABLE, expression), notNulls); } holder.problem(expression, JavaAnalysisBundle.message("inspection.nullable.problems.constructor.not.compatible.non.null.type.argument")) @@ -183,11 +182,10 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } Project project = holder.getProject(); if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) { - final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable(); + final String anno = + manager.getDefaultAnnotation(annotated.isDeclaredNotNull ? Nullability.NOT_NULL : Nullability.NULLABLE, field); final List annoToRemove = annotated.isDeclaredNotNull ? nullables : notNulls; - if (!checkNonStandardAnnotations(field, annotated, anno, holder)) return; - checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder); checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder); @@ -413,7 +411,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection Nullability typeNullability = DfaPsiUtil.getTypeNullability(type); if (typeNullability != Nullability.NOT_NULL && !(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType wildcardType && !wildcardType.isExtends())) { - String annotationToAdd = manager.getDefaultNotNull(); + String annotationToAdd = manager.getDefaultAnnotation(Nullability.NOT_NULL, reference); PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationToAdd, element.getResolveScope()); AddTypeAnnotationFix fix = null; if (annotationClass != null && @@ -624,23 +622,6 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null; } - private static boolean checkNonStandardAnnotations(PsiField field, - Annotated annotated, - String anno, @NotNull ProblemsHolder holder) { - if (!isAnnotatingApplicable(field, anno)) { - PsiAnnotation annotation = requireNonNull(annotated.isDeclaredNullable ? annotated.nullable : annotated.notNull); - String message = JavaAnalysisBundle.message("inspection.message.code.generation.different.nullability.annotation.will.be.used", - annotation.getQualifiedName(), anno); - final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement(); - holder.registerProblem(annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() ? annotationNameReferenceElement : field.getNameIdentifier(), - message, - ProblemHighlightType.WEAK_WARNING, - new ChangeNullableDefaultsFix(annotated.notNull, annotated.nullable)); - return false; - } - return true; - } - private void checkAccessors(PsiField field, Annotated annotated, Project project, @@ -882,7 +863,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } if (isNonAnnotatedOverridingNotNull(method, superMethod)) { - reportProblem(holder, identifier, createFixForNonAnnotatedOverridesNotNull(method, superMethod), + reportProblem(holder, identifier, createFixForNonAnnotatedOverridesNotNull(method), "inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)); break; } @@ -900,12 +881,11 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection return NullableNotNullManager.getInstance(method.getProject()); } - private static @Nullable LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method, - PsiMethod superMethod) { + private static @Nullable LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method) { NullableNotNullManager nullableManager = getNullityManager(method); return isAnnotatingApplicable(method, nullableManager.getDefaultNotNull()) ? LocalQuickFix.from(AddAnnotationModCommandAction.createAddNotNullFix(method)) - : createChangeDefaultNotNullFix(nullableManager, superMethod); + : null; } private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod) { @@ -975,9 +955,9 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters); if (notNullSuper != null) { - LocalQuickFix fix = isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull()) + LocalQuickFix fix = isAnnotatingApplicable(parameter, nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, parameter)) ? LocalQuickFix.from(AddAnnotationModCommandAction.createAddNotNullFix(parameter)) - : createChangeDefaultNotNullFix(nullableManager, notNullSuper); + : null; reportProblem(holder, nameIdentifier, fix, "inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(notNullSuper)); } @@ -1095,14 +1075,13 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } boolean checkReturnType = annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType); if (hasAnnotatedParameter || checkReturnType) { - final String defaultNotNull = nullableManager.getDefaultNotNull(); - final boolean superMethodApplicable = isAnnotatingApplicable(method, defaultNotNull); PsiMethod[] overridings = OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY); boolean methodQuickFixSuggested = false; for (PsiMethod overriding : overridings) { if (shouldSkipOverriderAsGenerated(overriding)) continue; + final String defaultNotNull = nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, overriding); if (!methodQuickFixSuggested && checkReturnType && !isNotNullNotInferred(overriding, false, false) @@ -1115,8 +1094,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection final String[] annotationsToRemove = ArrayUtilRt.toStringArray(nullableManager.getNullables()); LocalQuickFix fix = isAnnotatingApplicable(overriding, defaultNotNull) - ? new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove) - : superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method); + ? new MyAnnotateMethodFix(Nullability.NOT_NULL, annotationsToRemove) + : null; PsiElement psiElement = annotation; if (!annotation.isPhysical() || !PsiTreeUtil.isAncestor(method, annotation, true)) { @@ -1145,7 +1124,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } LocalQuickFix fix = isAnnotatingApplicable(parameter, defaultNotNull) ? new AnnotateOverriddenMethodParameterFix(Nullability.NOT_NULL, defaultNotNull) - : createChangeDefaultNotNullFix(nullableManager, parameters[i]); + : null; reportProblem(holder, psiElement, fix, "nullable.stuff.problems.overridden.method.parameters.are.not.annotated"); parameterQuickFixSuggested[i] = true; @@ -1183,28 +1162,12 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection (checkBases || info.getInheritedFrom() == null); } - private static LocalQuickFix createChangeDefaultNotNullFix(NullableNotNullManager nullableManager, PsiModifierListOwner modifierListOwner) { - final PsiAnnotation annotation = findAnnotation(modifierListOwner, nullableManager.getNotNulls()); - if (annotation != null) { - final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); - if (referenceElement != null) { - JavaResolveResult resolveResult = referenceElement.advancedResolve(false); - if (resolveResult.getElement() != null && - resolveResult.isValidResult() && - !nullableManager.getDefaultNotNull().equals(annotation.getQualifiedName())) { - return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null); - } - } - } - return null; - } - private static class MyAnnotateMethodFix implements LocalQuickFix { - protected final String myAnnotation; + private final @NotNull Nullability myNullability; private final String[] myAnnotationsToRemove; - MyAnnotateMethodFix(@NotNull String fqn, String @NotNull ... annotationsToRemove) { - myAnnotation = fqn; + MyAnnotateMethodFix(@NotNull Nullability nullability, String @NotNull ... annotationsToRemove) { + myNullability = nullability; myAnnotationsToRemove = annotationsToRemove.length == 0 ? ArrayUtilRt.EMPTY_STRING_ARRAY : annotationsToRemove; } @@ -1225,10 +1188,14 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection PsiMethod method = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class); if (method == null) return; final List toAnnotate = new ArrayList<>(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); if (!AnnotateOverriddenMethodParameterFix.processModifiableInheritorsUnderProgress(method, (Consumer)psiMethod -> { - if (isAnnotatingApplicable(psiMethod, myAnnotation) && - !isAnnotated(psiMethod, myAnnotation, CHECK_EXTERNAL | CHECK_TYPE)) { + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(psiMethod); + if (info != null && info.getNullability() == myNullability && !info.isInferred() && info.getInheritedFrom() == null) return; + String annotation = manager.getDefaultAnnotation(myNullability, psiMethod); + if (isAnnotatingApplicable(psiMethod, annotation) && + !isAnnotated(psiMethod, annotation, CHECK_EXTERNAL | CHECK_TYPE)) { toAnnotate.add(psiMethod); } })) { @@ -1237,7 +1204,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection FileModificationService.getInstance().preparePsiElementsForWrite(toAnnotate); for (PsiMethod psiMethod : toAnnotate) { - AddAnnotationPsiFix fix = new AddAnnotationPsiFix(myAnnotation, psiMethod, myAnnotationsToRemove); + String annotation = manager.getDefaultAnnotation(myNullability, psiMethod); + AddAnnotationPsiFix fix = new AddAnnotationPsiFix(annotation, psiMethod, myAnnotationsToRemove); fix.invoke(psiMethod.getProject(), psiMethod.getContainingFile(), psiMethod, psiMethod); } UndoUtil.markPsiFileForUndo(method.getContainingFile()); @@ -1250,8 +1218,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection @Override public @NotNull String getName() { - return JavaAnalysisBundle.message("inspection.annotate.overridden.method.nullable.quickfix.name", - ClassUtil.extractClassName(myAnnotation)); + return JavaAnalysisBundle.message("inspection.annotate.overridden.method.nullable.quickfix.name"); } } } diff --git a/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java b/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java index 7378b13ce89d..194971342187 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java @@ -15,6 +15,7 @@ */ package com.siyeh.ig.assignment; +import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.intention.AddAnnotationModCommandAction; import com.intellij.codeInspection.LocalQuickFix; @@ -58,7 +59,7 @@ public final class AssignmentToNullInspection extends BaseInspection { return null; } final NullableNotNullManager manager = NullableNotNullManager.getInstance(target.getProject()); - String annotation = manager.getDefaultNullable(); + String annotation = manager.getDefaultAnnotation(Nullability.NULLABLE, variable); if (JavaPsiFacade.getInstance(variable.getProject()).findClass(annotation, variable.getResolveScope()) == null) { return null; } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java index 5abae78932e9..12e872aa99c7 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java @@ -591,11 +591,11 @@ public class ExtractMethodDialog extends RefactoringDialog implements AbstractEx protected String getSignature() { final @NonNls StringBuilder buffer = new StringBuilder(); - if (myGenerateAnnotations != null && myGenerateAnnotations.isSelected()) { + if (myGenerateAnnotations != null && myGenerateAnnotations.isSelected() && myNullability != null) { final NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject); buffer.append("@"); buffer.append( - StringUtil.getShortName(myNullability == Nullability.NULLABLE ? nullManager.getDefaultNullable() : nullManager.getDefaultNotNull())); + StringUtil.getShortName(nullManager.getDefaultAnnotation(myNullability, myTargetClass))); buffer.append("\n"); } final int declarationOffset = buffer.length(); diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java index 844f05c20c06..588930a1bcdc 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java @@ -711,8 +711,9 @@ public class ExtractMethodProcessor implements MatchProvider { private Nullability initNullability() { if (!PsiUtil.isAvailable(JavaFeature.ANNOTATIONS, myElements[0]) || PsiUtil.resolveClassInType(myReturnType) == null) return null; final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); + String defaultAnnotation = manager.getDefaultAnnotation(Nullability.NULLABLE, myElements[0]); final PsiClass nullableAnnotationClass = JavaPsiFacade.getInstance(myProject) - .findClass(manager.getDefaultNullable(), myElements[0].getResolveScope()); + .findClass(defaultAnnotation, myElements[0].getResolveScope()); if (nullableAnnotationClass != null) { final PsiElement elementInCopy = myTargetClass.getContainingFile().copy().findElementAt(myTargetClass.getTextOffset()); final PsiClass classCopy = PsiTreeUtil.getParentOfType(elementInCopy, PsiClass.class); @@ -1076,9 +1077,9 @@ public class ExtractMethodProcessor implements MatchProvider { NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject); switch (myNullability) { case NOT_NULL -> - updateAnnotations(newMethod, nullManager.getNullables(), nullManager.getDefaultNotNull(), nullManager.getNotNulls()); + updateAnnotations(newMethod, nullManager.getNullables(), nullManager.getDefaultAnnotation(myNullability, myTargetClass), nullManager.getNotNulls()); case NULLABLE -> - updateAnnotations(newMethod, nullManager.getNotNulls(), nullManager.getDefaultNullable(), nullManager.getNullables()); + updateAnnotations(newMethod, nullManager.getNotNulls(), nullManager.getDefaultAnnotation(myNullability, myTargetClass), nullManager.getNullables()); default -> { } } } @@ -1691,7 +1692,7 @@ public class ExtractMethodProcessor implements MatchProvider { boolean isNotNull = nullability == Nullability.NOT_NULL; final List toKeep = isNotNull ? notNullAnnotations : nullableAnnotations; final List toRemove = isNotNull ? nullableAnnotations : notNullAnnotations; - final String toAdd = isNotNull ? nullabilityManager.getDefaultNotNull() : nullabilityManager.getDefaultNullable(); + final String toAdd = nullabilityManager.getDefaultAnnotation(nullability, parm); updateAnnotations(parm, toRemove, toAdd, toKeep); } } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt index 0905af3cea5f..f250e25c494c 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt @@ -55,8 +55,9 @@ object ExtractMethodHelper { fun isNullabilityAvailable(extractOptions: ExtractOptions): Boolean { val project = extractOptions.project - val scope = extractOptions.elements.first().resolveScope - val defaultNullable = NullableNotNullManager.getInstance(project).defaultNullable + val element = extractOptions.elements.first() + val scope = element.resolveScope + val defaultNullable = NullableNotNullManager.getInstance(project).getDefaultAnnotation(Nullability.NULLABLE, element) val annotationClass = JavaPsiFacade.getInstance(project).findClass(defaultNullable, scope) return annotationClass != null } @@ -119,12 +120,9 @@ object ExtractMethodHelper { fun addNullabilityAnnotation(typeElement: PsiTypeElement?, nullability: Nullability) { if (typeElement == null) return + if (nullability == Nullability.UNKNOWN) return val nullabilityManager = NullableNotNullManager.getInstance(typeElement.project) - val annotation = when (nullability) { - Nullability.NOT_NULL -> nullabilityManager.defaultNotNull - Nullability.NULLABLE -> nullabilityManager.defaultNullable - else -> return - } + val annotation = nullabilityManager.getDefaultAnnotation(nullability, typeElement) val annotationElement = AddAnnotationPsiFix.addPhysicalAnnotationIfAbsent(annotation, PsiNameValuePair.EMPTY_ARRAY, typeElement) if (annotationElement != null) { JavaCodeStyleManager.getInstance(typeElement.project).shortenClassReferences(annotationElement) diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java index 142b91423708..3f2002b0f3fc 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java @@ -3,6 +3,7 @@ package com.intellij.refactoring.extractMethodObject; import com.intellij.application.options.CodeStyle; +import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.generation.GenerateMembersUtil; import com.intellij.java.refactoring.JavaRefactoringBundle; @@ -630,7 +631,7 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor { final PsiModifierList modifierList = field.getModifierList(); LOG.assertTrue(modifierList != null); if (NullableNotNullManager.isNullable(parameter)) { - final String annotationName = NullableNotNullManager.getInstance(myProject).getDefaultNullable(); + final String annotationName = NullableNotNullManager.getInstance(myProject).getDefaultAnnotation(Nullability.NULLABLE, field); modifierList.addAfter(myElementFactory.createAnnotationFromText("@" + annotationName, field), null); } modifierList.setModifierProperty(PsiModifier.FINAL, isFinal); diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java index 3e21caeb2ec2..521b2b33dbf6 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java @@ -33,6 +33,7 @@ import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.SuggestedNameInfo; import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.util.PsiFormatUtil; import com.intellij.psi.util.PsiFormatUtilBase; @@ -561,6 +562,7 @@ public class IntroduceParameterHandler extends IntroduceHandlerBase { else { final PsiFile copy = PsiFileFactory.getInstance(project) .createFileFromText(file.getName(), file.getFileType(), file.getText(), file.getModificationStamp(), false); + ((PsiFileImpl)copy).setOriginalFile(file); final TextRange range = new TextRange(elements[0].getTextRange().getStartOffset(), elements[elements.length - 1].getTextRange().getEndOffset()); final PsiExpression exprInRange = CodeInsightUtil.findExpressionInRange(copy, range.getStartOffset(), range.getEndOffset()); diff --git a/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java b/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java index b7e4209af9e0..2b5cb5980a99 100644 --- a/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java +++ b/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java @@ -57,7 +57,7 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { List annotations = getAnnotationsToAdd(owner); if (!annotations.isEmpty()) { String presentation = StreamEx.of(annotations) - .map(MakeInferredAnnotationExplicit::getAnnotationPresentation) + .map(annotation -> getAnnotationPresentation(file, annotation)) .joining(" "); setText(CommonQuickFixBundle.message("fix.insert.x", presentation)); return true; @@ -76,8 +76,8 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { return ContainerUtil.filter(annotations, anno -> !isJetBrainsAnnotation(anno)); } - private static @NotNull String getAnnotationPresentation(PsiAnnotation annotation) { - final PsiJavaCodeReferenceElement nameRef = correctAnnotation(annotation).getNameReferenceElement(); + private static @NotNull String getAnnotationPresentation(@NotNull PsiFile file, PsiAnnotation annotation) { + final PsiJavaCodeReferenceElement nameRef = correctAnnotation(file, annotation).getNameReferenceElement(); final String name = nameRef != null ? nameRef.getReferenceName() : annotation.getQualifiedName(); return "@" + name + annotation.getParameterList().getText(); } @@ -160,11 +160,12 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { } private @Unmodifiable @NotNull List getAnnotationsToAdd(@NotNull PsiModifierListOwner owner) { + PsiFile file = owner.getContainingFile(); List allAnnotations = StreamEx.of(InferredAnnotationsManager.getInstance(owner.getProject()).findInferredAnnotations(owner)) .remove(DefaultInferredAnnotationProvider::isExperimentalInferredAnnotation) - .map(MakeInferredAnnotationExplicit::correctAnnotation) + .map(annotation -> correctAnnotation(file, annotation)) .toList(); - return filterAnnotations(owner.getContainingFile(), allAnnotations); + return filterAnnotations(file, allAnnotations); } private static void doMakeAnnotationExplicit(@NotNull Project project, @NotNull PsiModifierListOwner owner, @NotNull List annotations) { @@ -179,15 +180,15 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { } } - private static @NotNull PsiAnnotation correctAnnotation(@NotNull PsiAnnotation annotation) { + private static @NotNull PsiAnnotation correctAnnotation(@NotNull PsiFile file, @NotNull PsiAnnotation annotation) { Project project = annotation.getProject(); NullableNotNullManager nnnm = NullableNotNullManager.getInstance(project); PsiAnnotation corrected = null; if (annotation.hasQualifiedName(AnnotationUtil.NULLABLE)) { - corrected = createAnnotation(project, nnnm.getDefaultNullable()); + corrected = createAnnotation(project, nnnm.getDefaultAnnotation(Nullability.NULLABLE, file)); } else if (annotation.hasQualifiedName(AnnotationUtil.NOT_NULL)) { - corrected = createAnnotation(project, nnnm.getDefaultNotNull()); + corrected = createAnnotation(project, nnnm.getDefaultAnnotation(Nullability.NOT_NULL, file)); } return corrected != null ? corrected : annotation; } diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index f97b948c95a1..5f8900c512ab 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -11,12 +11,15 @@ import com.intellij.ide.highlighter.JavaFileType; import com.intellij.java.JavaBundle; import com.intellij.java.codeserver.core.JavaPsiModuleUtil; import com.intellij.java.library.JavaLibraryModificationTracker; +import com.intellij.java.library.JavaLibraryUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.extensions.ExtensionPointListener; import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.fileTypes.FileTypeRegistry; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; @@ -27,6 +30,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.ProjectInspectionProfileManager; import com.intellij.psi.*; import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex; +import com.intellij.psi.impl.source.DummyHolder; import com.intellij.psi.search.DelegatingGlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.CachedValueProvider.Result; @@ -61,6 +65,7 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private List myDefaultAll; public String myDefaultNullable = NULLABLE; public String myDefaultNotNull = NOT_NULL; + public boolean myOrdered = false; public final JDOMExternalizableStringList myNullables = new JDOMExternalizableStringList(); public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(); private List myInstrumentedNotNulls = List.of(NOT_NULL); @@ -85,11 +90,11 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private void updateDefaults() { myAnnotationSupports = AnnotationPackageSupport.EP_NAME.getExtensionList(); myDefaultNullables = StreamEx.of(myAnnotationSupports) - .cross(s -> s.getNullabilityAnnotations(Nullability.NULLABLE).stream()).invert().toMap(); + .cross(s -> s.getNullabilityAnnotations(Nullability.NULLABLE).stream()).invert().toCustomMap(LinkedHashMap::new); myDefaultNotNulls = StreamEx.of(myAnnotationSupports) - .cross(s -> s.getNullabilityAnnotations(Nullability.NOT_NULL).stream()).invert().toMap(); + .cross(s -> s.getNullabilityAnnotations(Nullability.NOT_NULL).stream()).invert().toCustomMap(LinkedHashMap::new); myDefaultUnknowns = StreamEx.of(myAnnotationSupports) - .cross(s -> s.getNullabilityAnnotations(Nullability.UNKNOWN).stream()).invert().toMap(); + .cross(s -> s.getNullabilityAnnotations(Nullability.UNKNOWN).stream()).invert().toCustomMap(LinkedHashMap::new); myDefaultAll = StreamEx.of(myDefaultNullables, myDefaultNotNulls, myDefaultUnknowns).toFlatList(Map::keySet); normalizeDefaults(); } @@ -108,6 +113,39 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement normalizeDefaults(); } + @Override + public @NotNull String getDefaultAnnotation(@NotNull Nullability nullability, @NotNull PsiElement context) { + Collection annotations = switch(nullability) { + case NOT_NULL -> myNotNulls; + case NULLABLE -> myNullables; + case UNKNOWN -> myDefaultUnknowns.keySet(); + }; + PsiFile containingFile = context.getContainingFile(); + if (containingFile instanceof DummyHolder) { + PsiElement element = containingFile.getContext(); + if (element != null) { + containingFile = element.getContainingFile(); + } + } + PsiFile file = containingFile.getOriginalFile(); + Module module = ModuleUtilCore.findModuleForFile(file); + if (module == null) return getDefaultAnnotation(nullability); + for (String annotation : annotations) { + if (JavaLibraryUtil.hasLibraryClass(module, annotation)) { + return annotation; + } + } + return getDefaultAnnotation(nullability); + } + + private @NotNull String getDefaultAnnotation(@NotNull Nullability nullability) { + return switch (nullability) { + case NOT_NULL -> getDefaultNotNull(); + case NULLABLE -> getDefaultNullable(); + case UNKNOWN -> AnnotationUtil.UNKNOWN_NULLABILITY; + }; + } + @Override public @NotNull String getDefaultNullable() { return myDefaultNullable; @@ -227,15 +265,16 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private boolean hasDefaultValues() { return NOT_NULL.equals(myDefaultNotNull) && NULLABLE.equals(myDefaultNullable) && - new HashSet<>(myNullables).equals(myDefaultNullables.keySet()) && - new HashSet<>(myNotNulls).equals(myDefaultNotNulls.keySet()); + myNullables.equals(new ArrayList<>(myDefaultNullables.keySet())) && + myNotNulls.equals(new ArrayList<>(myDefaultNotNulls.keySet())); } - + @Override public void loadState(@NotNull Element state) { try { DefaultJDOMExternalizer.readExternal(this, state); normalizeDefaults(); + migrateSettings(); } catch (InvalidDataException e) { LOG.error(e); @@ -250,6 +289,31 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement } } + /** + * Convert old settings when the order of annotations didn't matter: + * 1. Put the default annotation on top, unless it's JetBrains default annotation + * 2. Next, put all the custom annotations + * 3. Next, put all annotations from {@link AnnotationPackageSupport} extensions in the order they are provided + */ + private void migrateSettings() { + if (!myOrdered) { + myOrdered = true; + // Set the default order and put default to the front + myNullables.removeAll(myDefaultNullables.keySet()); + myNullables.addAll(myDefaultNullables.keySet()); + if (!myDefaultNullable.equals(NULLABLE)) { + myNullables.remove(myDefaultNullable); + myNullables.add(0, myDefaultNullable); + } + myNotNulls.removeAll(myDefaultNotNulls.keySet()); + myNotNulls.addAll(myDefaultNotNulls.keySet()); + if (!myDefaultNotNull.equals(NOT_NULL)) { + myNotNulls.remove(myDefaultNotNull); + myNotNulls.add(0, myDefaultNotNull); + } + } + } + private void normalizeDefaults() { myNotNulls.removeAll(myDefaultNullables.keySet()); myNullables.removeAll(myDefaultNotNulls.keySet()); diff --git a/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java b/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java index 60d75c1464b1..951c7d42a29b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java +++ b/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java @@ -3,15 +3,19 @@ package com.intellij.codeInsight.annoPackages; import com.intellij.codeInsight.ContextNullabilityInfo; import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; /** - * Support for custom annotation packages + * Support for custom annotation packages. + * The registration order of extension matters, as the annotations returned from the first {@link #getNullabilityAnnotations(Nullability)} + * will be preferred by default. */ public interface AnnotationPackageSupport { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.lang.jvm.annotationPackageSupport"); @@ -32,7 +36,9 @@ public interface AnnotationPackageSupport { /** * @param nullability desired nullability - * @return list of explicit annotations which denote given nullability (and may denote additional semantics) + * @return list of explicit annotations which denote given nullability (and may denote additional semantics). + * The annotation returned first will be preferred by default + * in {@link NullableNotNullManager#getDefaultAnnotation(Nullability, PsiElement)}. */ default @NotNull List getNullabilityAnnotations(@NotNull Nullability nullability) { return Collections.emptyList(); diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java index 8169958e1192..481189cdb955 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java @@ -3,6 +3,7 @@ package com.intellij.codeInsight.completion; import com.intellij.codeInsight.AutoPopupController; import com.intellij.codeInsight.ExpectedTypesProvider; +import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.lookup.Lookup; import com.intellij.codeInsight.lookup.LookupElement; @@ -109,7 +110,7 @@ class JavaClassNameInsertHandler implements InsertHandler toRemove) { + new AddAnnotationPsiFix(fqn, element, ArrayUtil.toStringArray(toRemove)).applyFix(); } public int getCount() { return myNotNullSet.size() + myNullableSet.size(); } - public static boolean apply(@NotNull Project project, @NotNull NullableNotNullManager manager, UsageInfo info) { + public static boolean apply(@NotNull NullableNotNullManager manager, UsageInfo info) { if (info instanceof NullableUsageInfo) { return annotateNullable(manager, (PsiModifierListOwner)info.getElement()); } diff --git a/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java b/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java index 843c431f6cb6..f47025c3ab5a 100644 --- a/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java +++ b/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java @@ -1,7 +1,6 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.siyeh.ig.bugs; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullabilityAnnotationInfo; import com.intellij.codeInsight.NullableNotNullManager; @@ -20,7 +19,6 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.controlFlow.DefUseUtil; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ArrayUtilRt; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; @@ -79,10 +77,6 @@ public final class ReturnNullInspection extends BaseInspection { @Override protected @Nullable LocalQuickFix buildFix(Object... infos) { final PsiElement elt = (PsiElement)infos[0]; - if (!AnnotationUtil.isAnnotatingApplicable(elt)) { - return null; - } - final PsiMethod method = PsiTreeUtil.getParentOfType(elt, PsiMethod.class, false, PsiLambdaExpression.class); if (method == null) return null; final PsiType type = method.getReturnType(); @@ -91,9 +85,7 @@ public final class ReturnNullInspection extends BaseInspection { return new ReplaceWithEmptyOptionalFix(((PsiClassType)type).rawType().getCanonicalText()); } - final NullableNotNullManager manager = NullableNotNullManager.getInstance(elt.getProject()); - return LocalQuickFix.from(new AddAnnotationModCommandAction(manager.getDefaultNullable(), method, - ArrayUtilRt.toStringArray(manager.getNotNulls()))); + return LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(method)); } @Override diff --git a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java index 83463bbdae35..df85d25aabf3 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java @@ -382,10 +382,6 @@ public class AnnotationUtil { return false; } - public static boolean isAnnotatingApplicable(@NotNull PsiElement elt) { - return isAnnotatingApplicable(elt, NullableNotNullManager.getInstance(elt.getProject()).getDefaultNullable()); - } - public static boolean isAnnotatingApplicable(@NotNull PsiElement elt, @NotNull String annotationFQN) { final Project project = elt.getProject(); return PsiUtil.isAvailable(JavaFeature.ANNOTATIONS, elt) && diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index c1c286891640..b7b75a1f60b9 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -57,12 +57,45 @@ public abstract class NullableNotNullManager { public abstract void setNullables(String @NotNull ... annotations); + /** + * @param nullability wanted nullability + * @param context PSI context + * @return the best suitable annotation to insert in a specified context + */ + public abstract @NotNull String getDefaultAnnotation(@NotNull Nullability nullability, @NotNull PsiElement context); + + /** + * @return default nullable annotation, + * used for external and inferred annotations, or as a fallback when no other annotation is available + * @see #getDefaultAnnotation(Nullability, PsiElement) + */ public abstract @NotNull String getDefaultNullable(); + /** + * Sets the default nullable annotation, used for external and inferred annotations, + * or as a fallback when no other annotation is available. + * + * @param defaultNullable new default nullable annotation + * @see #getDefaultNullable() + */ public abstract void setDefaultNullable(@NotNull String defaultNullable); + /** + * @return default not-null annotation, + * used for external and inferred annotations, or as a fallback when no other annotation is available + * @see #getDefaultAnnotation(Nullability, PsiElement) + */ public abstract @NotNull String getDefaultNotNull(); + /** + * Sets the default not-null annotation, used for external and inferred annotations, + * or as a fallback when no other annotation is available. + * + * @param defaultNotNull new default nullable annotation + * @see #getDefaultNotNull() + */ + public abstract void setDefaultNotNull(@NotNull String defaultNotNull); + public void copyNotNullAnnotation(@NotNull PsiModifierListOwner original, @NotNull PsiModifierListOwner generated) { NullabilityAnnotationInfo info = findOwnNullabilityInfo(original); if (info == null || info.getNullability() != Nullability.NOT_NULL) return; @@ -110,8 +143,6 @@ public abstract class NullableNotNullManager { return null; } - public abstract void setDefaultNotNull(@NotNull String defaultNotNull); - /** * Returns own nullability annotation info for given element. Returned annotation is not inherited and * not container annotation for class/package. Still it could be inferred or external. diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java index 99869df0bb52..6bd3f49d8599 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java @@ -1,4 +1,4 @@ -// "Annotate overriding methods as '@NotNull'" "true" +// "Add missing nullability annotation to overriding methods" "true" import org.jetbrains.annotations.NotNull; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java index 15d60a531b0b..0b9d188a1004 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java @@ -1,4 +1,4 @@ -// "Annotate overriding methods as '@NotNull'" "true" +// "Add missing nullability annotation to overriding methods" "true" import org.jetbrains.annotations.NotNull; diff --git a/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java b/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java index 96473d5f021e..86902293cfe5 100644 --- a/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java @@ -1,9 +1,12 @@ +import org.jetbrains.annotations.NotNull; + interface TweetParser { static String getTweetMessageFrom(String fullTweet) { String fieldName = "\"text\":\""; return newMethod(fullTweet, fieldName); } + @NotNull static String newMethod(String fullTweet, String fieldName) { int indexOfField = fullTweet.indexOf(fieldName) + fieldName.length(); int indexOfEndOfField = fullTweet.indexOf("\"", indexOfField); diff --git a/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java b/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java index 96473d5f021e..86902293cfe5 100644 --- a/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java +++ b/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java @@ -1,9 +1,12 @@ +import org.jetbrains.annotations.NotNull; + interface TweetParser { static String getTweetMessageFrom(String fullTweet) { String fieldName = "\"text\":\""; return newMethod(fullTweet, fieldName); } + @NotNull static String newMethod(String fullTweet, String fieldName) { int indexOfField = fullTweet.indexOf(fieldName) + fieldName.length(); int indexOfEndOfField = fullTweet.indexOf("\"", indexOfField); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java index 2069c3b69272..cced6c439b01 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java @@ -38,7 +38,7 @@ public class AnnotateMethodInGeneratedFilesTest extends LightJavaCodeInsightFixt } public void testAnnotateOverriddenMethod() { - doTest("Annotate overriding methods"); + doTest("Add missing nullability annotation"); } public void testAnnotateOverriddenParameters() { diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java index dc92c8bcc2e2..ded9534ece55 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java @@ -7,10 +7,8 @@ import com.intellij.codeInsight.annoPackages.AnnotationPackageSupport; import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.nullable.NullableStuffInspection; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.extensions.LoadingOrder; import com.intellij.pom.java.LanguageLevel; -import com.intellij.testFramework.ServiceContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -25,7 +23,6 @@ public class AnnotateMethodTest extends LightQuickFixParameterizedTestCase { protected void setUp() throws Exception { super.setUp(); if (getTestName(false).contains("TypeUse")) { - NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); AnnotationPackageSupport mySupport = new AnnotationPackageSupport() { @Override public @NotNull List getNullabilityAnnotations(@NotNull Nullability nullability) { @@ -40,17 +37,10 @@ public class AnnotateMethodTest extends LightQuickFixParameterizedTestCase { public boolean isTypeUseAnnotationLocationRestricted() { return true; } - }; - ServiceContainerUtil.registerExtension(ApplicationManager.getApplication(), AnnotationPackageSupport.EP_NAME, mySupport, getTestRootDisposable()); - String prevNullable = nnnManager.getDefaultNullable(); - String prevNotNull = nnnManager.getDefaultNotNull(); - nnnManager.setDefaultNotNull("typeUse.NotNull"); - nnnManager.setDefaultNullable("typeUse.Nullable"); - Disposer.register(getTestRootDisposable(), () -> { - nnnManager.setDefaultNotNull(prevNotNull); - nnnManager.setDefaultNullable(prevNullable); - }); + }; + AnnotationPackageSupport.EP_NAME.getPoint().registerExtension(mySupport, LoadingOrder.FIRST, getTestRootDisposable()); } + NullableNotNullManager.getInstance(getProject()).setNotNulls(); // to normalize defaults } @Override diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java index 758dde125a54..785ec7a55f4e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java @@ -1,6 +1,5 @@ package com.intellij.java.codeInsight; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; @@ -50,7 +49,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt public void test_custom_notNull() { myFixture.addClass("package foo; public @interface MyNotNull {}"); NullableNotNullManager.getInstance(getProject()).setNotNulls("foo.MyNotNull"); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull("foo.MyNotNull"); myFixture.configureByText("a.java", """ class Foo { @@ -75,7 +73,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt public void test_type_use() { myFixture.addClass("package foo; import java.lang.annotation.*;@Target(ElementType.TYPE_USE)public @interface MyNotNull {}"); NullableNotNullManager.getInstance(getProject()).setNotNulls("foo.MyNotNull"); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull("foo.MyNotNull"); myFixture.configureByText("a.java", """ class Foo { static void foo(String[] array) { @@ -96,7 +93,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt public void test_type_use_qualified_type() { myFixture.addClass("package foo; import java.lang.annotation.*;@Target(ElementType.TYPE_USE)public @interface MyNotNull {}"); NullableNotNullManager.getInstance(getProject()).setNotNulls("foo.MyNotNull"); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull("foo.MyNotNull"); myFixture.configureByText("a.java", """ import org.jetbrains.annotations.Contract; import foo.MyNotNull; @@ -125,7 +121,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt protected void tearDown() throws Exception { try { NullableNotNullManager.getInstance(getProject()).setNotNulls(ArrayUtil.EMPTY_STRING_ARRAY); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull(AnnotationUtil.NOT_NULL); } catch (Throwable e) { addSuppressedException(e); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java index 2fb1da3f8069..0d07049ed52d 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java @@ -64,24 +64,16 @@ public class OverrideImplement15Test extends LightJavaCodeInsightTestCase { private void doCustomNotNullAnnotations() { NullableNotNullManager nullableNotNullManager = NullableNotNullManager.getInstance(getProject()); - String defaultNotNull = nullableNotNullManager.getDefaultNotNull(); List notNulls = nullableNotNullManager.getNotNulls(); - String defaultNullable = nullableNotNullManager.getDefaultNullable(); List nullables = nullableNotNullManager.getNullables(); try { - nullableNotNullManager.setNotNulls(ArrayUtil.append(ArrayUtil.toStringArray(notNulls),"p.NN")); - nullableNotNullManager.setDefaultNotNull("p.NN"); - - nullableNotNullManager.setNullables(ArrayUtil.append(ArrayUtil.toStringArray(nullables),"p.N")); - nullableNotNullManager.setDefaultNullable("p.N"); + nullableNotNullManager.setNotNulls(ArrayUtil.prepend("p.NN",ArrayUtil.toStringArray(notNulls))); + nullableNotNullManager.setNullables(ArrayUtil.prepend("p.N",ArrayUtil.toStringArray(nullables))); doTest(true, true); } finally { - nullableNotNullManager.setDefaultNotNull(defaultNotNull); nullableNotNullManager.setNotNulls(ArrayUtil.toStringArray(notNulls)); - - nullableNotNullManager.setDefaultNullable(defaultNullable); nullableNotNullManager.setNullables(ArrayUtil.toStringArray(nullables)); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index b022e0f4ab6d..036fab92629b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -2,11 +2,9 @@ package com.intellij.java.codeInspection; import com.intellij.JavaTestUtil; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInspection.dataFlow.ConstantValueInspection; import com.intellij.codeInspection.dataFlow.DataFlowInspection; -import com.intellij.openapi.util.Disposer; import com.intellij.testFramework.LightProjectDescriptor; import org.jetbrains.annotations.NotNull; @@ -306,24 +304,10 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { } public void testArrayTypeParameterInference() { setupTypeUseAnnotations("typeUse", myFixture); - NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); - nnnManager.setDefaultNotNull("typeUse.NotNull"); - nnnManager.setDefaultNullable("typeUse.Nullable"); - Disposer.register(getTestRootDisposable(), () -> { - nnnManager.setDefaultNotNull(AnnotationUtil.NOT_NULL); - nnnManager.setDefaultNullable(AnnotationUtil.NULLABLE); - }); doTest(); } public void testArrayTypeParameterInferenceAmbiguous() { setupAmbiguousAnnotations("ambiguous", myFixture); - NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); - nnnManager.setDefaultNotNull("ambiguous.NotNull"); - nnnManager.setDefaultNullable("ambiguous.Nullable"); - Disposer.register(getTestRootDisposable(), () -> { - nnnManager.setDefaultNotNull(AnnotationUtil.NOT_NULL); - nnnManager.setDefaultNullable(AnnotationUtil.NULLABLE); - }); doTest(); } public void testGuavaFunction() { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java index 0c2c821c2e7d..054449429f88 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java @@ -384,15 +384,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest public void testQuickFixOnTypeArgument() { setupTypeUseAnnotations("typeUse", myFixture); - NullableNotNullManager manager = NullableNotNullManager.getInstance(getProject()); - String oldDefault = manager.getDefaultNotNull(); - try { - manager.setDefaultNotNull("typeUse.NotNull"); - doTestWithFix("Annotate as '@NotNull'"); - } - finally { - manager.setDefaultNotNull(oldDefault); - } + doTestWithFix("Annotate as '@NotNull'"); } public void testRemoveAnnotationWithImportQuickFix() { @@ -401,15 +393,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest public void testQuickFixOnTypeArgumentNullable() { setupTypeUseAnnotations("typeUse", myFixture); - NullableNotNullManager manager = NullableNotNullManager.getInstance(getProject()); - String oldDefault = manager.getDefaultNotNull(); - try { - manager.setDefaultNotNull("typeUse.NotNull"); - doTestWithFix("Annotate as '@NotNull'"); - } - finally { - manager.setDefaultNotNull(oldDefault); - } + doTestWithFix("Annotate as '@NotNull'"); } public void testCheckerDefaultTypeUseRecursiveGeneric() { @@ -463,10 +447,6 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest } public void testIncompatibleConstructors() { - final NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); - String nullable = nnnManager.getDefaultNullable(); - nnnManager.setDefaultNullable("org.jspecify.annotations.Nullable"); - Disposer.register(myFixture.getTestRootDisposable(), () -> nnnManager.setDefaultNullable(nullable)); addJSpecifyNullMarked(myFixture); setupTypeUseAnnotations("org.jspecify.annotations", myFixture); doTest(); diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index c51b94bf4f89..b0a3bcc1d183 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -1082,7 +1082,9 @@ node.call.hierarchy.unknown.jsp=unknown jsp node.hierarchy.java.lang.object=All classes are derived from java.lang.Object notification.navigation.to.overriding.classes=Navigation to overriding classes is not possible during index update notification.navigation.to.overriding.methods=Navigation to overriding methods is not possible during index update +annotations.panel.title={0} annotations: nullable.notnull.annotations.panel.title={0} annotations: +nullable.notnull.annotations.panel.description=The first from this list available in the project will be suggested as a quick-fix nullable.notnull.annotations.runtime.instrumentation.tooltip=Add runtime assertions for notnull-annotated methods and parameters nullable.notnull.configuration.dialog.title=Nullable/NotNull Configuration nullable.notnull.annotation.used.label=Annotation used for code generation: diff --git a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java index 5eeeb11bf542..3e2bf7a8c8dd 100644 --- a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java +++ b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java @@ -1,14 +1,10 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeInsight; -import com.intellij.core.JavaPsiBundle; import com.intellij.ide.util.ClassFilter; import com.intellij.ide.util.TreeClassChooser; import com.intellij.ide.util.TreeClassChooserFactory; import com.intellij.java.JavaBundle; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ReadAction; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.NlsSafe; @@ -18,23 +14,20 @@ import com.intellij.ui.*; import com.intellij.ui.dsl.builder.DslComponentProperty; import com.intellij.ui.dsl.builder.VerticalComponentGap; import com.intellij.ui.table.JBTable; -import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.UI; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.table.*; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableRowSorter; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.IntStream; -import java.util.stream.Stream; public class AnnotationsPanel { private final Project myProject; @@ -47,32 +40,16 @@ public class AnnotationsPanel { public AnnotationsPanel(Project project, @NonNls String name, - String defaultAnnotation, List annotations, - List defaultAnnotations, - Set checkedAnnotations, - boolean showInstrumentationOptions, - boolean showDefaultActions) { - this(project, new SimpleAnnotationPanelModel(name, defaultAnnotation, annotations, defaultAnnotations, checkedAnnotations), showInstrumentationOptions, showDefaultActions); - } - - public AnnotationsPanel(Project project, - @NotNull AnnotationPanelModel model, - boolean showInstrumentationOptions, - boolean showDefaultActions) { + List defaultAnnotations) { myProject = project; - myDefaultAnnotations = new HashSet<>(model.getDefaultAnnotations()); + myDefaultAnnotations = new HashSet<>(defaultAnnotations); - List annotations = model.getAnnotations(); myCombo = new ComboBox<>(annotations.stream().sorted().toArray(String[]::new)); - String defaultAnnotation = model.getDefaultAnnotation(); - if (!annotations.contains(defaultAnnotation)) { - addAnnotationToCombo(defaultAnnotation); + if (!annotations.contains("")) { + addAnnotationToCombo(""); } - if (model.hasAdvancedAnnotations()) { - loadAdvancedAnnotations(model); - } - myCombo.setSelectedItem(defaultAnnotation); + myCombo.setSelectedItem(""); myTableModel = new DefaultTableModel() { @Override @@ -80,9 +57,9 @@ public class AnnotationsPanel { return column == 1; } }; - myTableModel.setColumnCount(showInstrumentationOptions ? 2 : 1); + myTableModel.setColumnCount(1); for (String annotation : annotations) { - addRow(annotation, model.getCheckedAnnotations().contains(annotation)); + addRow(annotation); } DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); @@ -107,41 +84,11 @@ public class AnnotationsPanel { mySorter = new TableRowSorter<>(myTableModel); mySorter.setSortKeys(List.of(new RowSorter.SortKey(0, SortOrder.ASCENDING))); myTable.setRowSorter(mySorter); - if (!showInstrumentationOptions) myTable.setTableHeader(null); + myTable.setTableHeader(null); mySorter.sort(); - if (showInstrumentationOptions) { - columnModel.getColumn(0).setHeaderValue(JavaPsiBundle.message("node.annotation.tooltip")); - - TableColumn checkColumn = new TableColumn(1, 100, new BooleanTableCellRenderer(), new BooleanTableCellEditor()); - columnModel.addColumn(checkColumn); - checkColumn.setHeaderValue(" Instrument "); - - TableCellRenderer defaultRenderer = myTable.getTableHeader().getDefaultRenderer(); - - TableCellRenderer headerRenderer = new TableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, - int column) { - Component component = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (component instanceof JComponent) { - ((JComponent)component) - .setToolTipText(column == 1 ? JavaBundle.message("nullable.notnull.annotations.runtime.instrumentation.tooltip") : null); - } - return component; - } - }; - myTable.getTableHeader().setDefaultRenderer(headerRenderer); - checkColumn.setHeaderRenderer(headerRenderer); - checkColumn.sizeWidthToFit(); - } - final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myTable).disableUpDownActions() - .setAddAction(b -> chooseAnnotation(model.getName())) + .setAddAction(b -> chooseAnnotation(name)) .setRemoveAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { @@ -167,7 +114,7 @@ public class AnnotationsPanel { final var tablePanel = UI.PanelFactory .panel(toolbarDecorator.createPanel()) - .withLabel(JavaBundle.message("nullable.notnull.annotations.panel.title", model.getName())) + .withLabel(JavaBundle.message("annotations.panel.title", name)) .moveLabelOnTop() .resizeY(true) .createPanel(); @@ -178,15 +125,6 @@ public class AnnotationsPanel { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.weightx = 1; - if (showDefaultActions) { - myComponent.add(new JLabel(JavaBundle.message("nullable.notnull.annotation.used.label")), constraints); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets.bottom = 3; - constraints.gridy = 1; - myComponent.add(myCombo, constraints); - constraints.insets.bottom = 0; - constraints.gridy = 2; - } constraints.fill = GridBagConstraints.BOTH; constraints.weighty = 1; myComponent.add(tablePanel, constraints); @@ -196,42 +134,8 @@ public class AnnotationsPanel { return true; } - private void loadAdvancedAnnotations(@NotNull AnnotationPanelModel model) { - // No project-specific annotations are possible for default project - if (myProject.isDefault()) return; - String loading = JavaBundle.message("loading.additional.annotations"); - myCombo.addItem(loading); - DumbService.getInstance(myProject).runWhenSmart(() -> { - ReadAction.nonBlocking(model::getAdvancedAnnotations) - .finishOnUiThread(ModalityState.any(), advancedAnnotations -> { - myCombo.removeItem(loading); - int count = myCombo.getItemCount(); - Object selectedItem = myCombo.getSelectedItem(); - List newItems = Stream.concat( - IntStream.range(0, count).mapToObj(myCombo::getItemAt), - advancedAnnotations.stream()).sorted().distinct().toList(); - myCombo.removeAllItems(); - newItems.forEach(myCombo::addItem); - myCombo.setSelectedItem(selectedItem); - }).submit(AppExecutorUtil.getAppExecutorService()); - }); - myCombo.addActionListener(new ActionListener() { - Object previous = myCombo.getSelectedItem(); - - @Override - public void actionPerformed(ActionEvent e) { - Object item = myCombo.getSelectedItem(); - if (item == loading) { - myCombo.setSelectedItem(previous); - } else { - previous = item; - } - } - }); - } - - private void addRow(String annotation, boolean checked) { - myTableModel.addRow(new Object[]{annotation, checked}); + private void addRow(String annotation) { + myTableModel.addRow(new Object[]{annotation}); } private Integer selectAnnotation(String annotation) { @@ -264,7 +168,7 @@ public class AnnotationsPanel { } final String qualifiedName = selected.getQualifiedName(); if (selectAnnotation(qualifiedName) == null) { - addRow(qualifiedName, false); + addRow(qualifiedName); addAnnotationToCombo(qualifiedName); mySorter.sort(); Object added = selectAnnotation(qualifiedName); @@ -285,10 +189,6 @@ public class AnnotationsPanel { return myComponent; } - String getDefaultAnnotation() { - return myCombo.getItem(); - } - public String[] getAnnotations() { int size = myTable.getRowCount(); String[] result = new String[size]; @@ -298,16 +198,6 @@ public class AnnotationsPanel { return result; } - List getCheckedAnnotations() { - List result = new ArrayList<>(); - for (int i = 0; i < myTable.getRowCount(); i++) { - if (Boolean.TRUE.equals(myTable.getValueAt(i, 1))) { - result.add((String)myTable.getValueAt(i, 0)); - } - } - return result; - } - /** Reset table to contain only annotations from the list. */ public void resetAnnotations(List annotations) { final Set set = new HashSet<>(annotations); @@ -321,57 +211,7 @@ public class AnnotationsPanel { } } for (String annotation : set) { - addRow(annotation, false); - } - } - - private static class SimpleAnnotationPanelModel implements AnnotationPanelModel { - private final @NonNls String myName; - private final String myDefaultAnnotation; - private final List myAnnotations; - private final List myDefaultAnnotations; - private final Set myCheckedAnnotations; - - private SimpleAnnotationPanelModel(@NonNls String name, - String defaultAnnotation, - List annotations, - List defaultAnnotations, - Set checkedAnnotations) { - myName = name; - myDefaultAnnotation = defaultAnnotation; - myAnnotations = annotations; - myDefaultAnnotations = defaultAnnotations; - myCheckedAnnotations = checkedAnnotations; - } - - @Override - public @NotNull String getName() { - return myName; - } - - @Override - public @NotNull String getDefaultAnnotation() { - return myDefaultAnnotation; - } - - @Override - public @NotNull List getAnnotations() { - return myAnnotations; - } - - @Override - public @NotNull List getAdvancedAnnotations() { - return myAnnotations; - } - - @Override - public @NotNull List getDefaultAnnotations() { - return myDefaultAnnotations; - } - - @Override - public @NotNull Set getCheckedAnnotations() { - return myCheckedAnnotations; + addRow(annotation); } } } \ No newline at end of file diff --git a/java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java b/java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java new file mode 100644 index 000000000000..75a7d67baa0a --- /dev/null +++ b/java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java @@ -0,0 +1,319 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight; + +import com.intellij.core.JavaPsiBundle; +import com.intellij.ide.util.ClassFilter; +import com.intellij.ide.util.TreeClassChooser; +import com.intellij.ide.util.TreeClassChooserFactory; +import com.intellij.java.JavaBundle; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.ui.*; +import com.intellij.ui.dsl.builder.DslComponentProperty; +import com.intellij.ui.dsl.builder.VerticalComponentGap; +import com.intellij.ui.table.JBTable; +import com.intellij.util.concurrency.AppExecutorUtil; +import com.intellij.util.ui.JBDimension; +import com.intellij.util.ui.UI; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class NullableAnnotationsPanel { + private final Project myProject; + private final Set myDefaultAnnotations; + private final JBTable myTable; + private final JPanel myComponent; + private final ComboBox myCombo; + protected final DefaultTableModel myTableModel; + + public NullableAnnotationsPanel(Project project, + @NotNull AnnotationPanelModel model, + boolean showInstrumentationOptions) { + myProject = project; + myDefaultAnnotations = new HashSet<>(model.getDefaultAnnotations()); + + List annotations = model.getAnnotations(); + myCombo = new ComboBox<>(annotations.stream().sorted().toArray(String[]::new)); + String defaultAnnotation = model.getDefaultAnnotation(); + if (!annotations.contains(defaultAnnotation)) { + addAnnotationToCombo(defaultAnnotation); + } + if (model.hasAdvancedAnnotations()) { + loadAdvancedAnnotations(model); + } + myCombo.setSelectedItem(defaultAnnotation); + + myTableModel = new DefaultTableModel() { + @Override + public boolean isCellEditable(int row, int column) { + return column == 1; + } + }; + myTableModel.setColumnCount(showInstrumentationOptions ? 2 : 1); + for (String annotation : annotations) { + addRow(annotation, model.getCheckedAnnotations().contains(annotation)); + } + + DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); + columnModel.addColumn(new TableColumn(0, 100, new ColoredTableCellRenderer() { + @Override + public void acquireState(JTable table, boolean isSelected, boolean hasFocus, int row, int column) { + super.acquireState(table, isSelected, false, row, column); + } + + @Override + protected void customizeCellRenderer(@NotNull JTable table, + Object value, + boolean selected, + boolean hasFocus, + int row, + int column) { + append((String)value, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + }, null)); + + myTable = new JBTable(myTableModel, columnModel); + if (!showInstrumentationOptions) myTable.setTableHeader(null); + + if (showInstrumentationOptions) { + columnModel.getColumn(0).setHeaderValue(JavaPsiBundle.message("node.annotation.tooltip")); + + TableColumn checkColumn = new TableColumn(1, 100, new BooleanTableCellRenderer(), new BooleanTableCellEditor()); + columnModel.addColumn(checkColumn); + checkColumn.setHeaderValue(" Instrument "); + + TableCellRenderer headerRenderer = createHeaderRenderer(); + myTable.getTableHeader().setDefaultRenderer(headerRenderer); + checkColumn.setHeaderRenderer(headerRenderer); + checkColumn.sizeWidthToFit(); + } + + final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myTable) + .setMoveUpAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + int selectedRow = myTable.getSelectedRow(); + if (selectedRow < 1) return; + @SuppressWarnings("unchecked") + List vector = myTableModel.getDataVector().get(selectedRow); + myTableModel.removeRow(selectedRow); + myTableModel.insertRow(selectedRow - 1, vector.toArray()); + myTable.setRowSelectionInterval(selectedRow - 1, selectedRow - 1); + } + }) + .setMoveDownAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + int selectedRow = myTable.getSelectedRow(); + if (selectedRow < 0 || selectedRow >= myTableModel.getRowCount() - 1) return; + @SuppressWarnings("unchecked") + List vector = myTableModel.getDataVector().get(selectedRow); + myTableModel.removeRow(selectedRow); + myTableModel.insertRow(selectedRow + 1, vector.toArray()); + myTable.setRowSelectionInterval(selectedRow + 1, selectedRow + 1); + } + }) + .setAddAction(b -> chooseAnnotation(model.getName())) + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton anActionButton) { + String selectedValue = getSelectedAnnotation(); + if (selectedValue == null) return; + myCombo.removeItem(selectedValue); + + int rowIndex = -1; + for (int i = 0; i < myTableModel.getDataVector().size(); i++) { + if (myTableModel.getDataVector().get(i).contains(selectedValue)) { + rowIndex = i; + break; + } + } + if (rowIndex != -1) myTableModel.removeRow(rowIndex); + } + }) + .setRemoveActionUpdater(e -> !myDefaultAnnotations.contains(getSelectedAnnotation())); + + myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myTable.setRowSelectionAllowed(true); + myTable.setShowGrid(false); + + final var tablePanel = UI.PanelFactory + .panel(toolbarDecorator.createPanel()) + .withLabel(JavaBundle.message("nullable.notnull.annotations.panel.title", model.getName())) + .withComment(JavaBundle.message("nullable.notnull.annotations.panel.description")) + .moveLabelOnTop() + .resizeY(true) + .createPanel(); + tablePanel.setPreferredSize(new JBDimension(tablePanel.getPreferredSize().width, 200)); + + myComponent = new JPanel(new GridBagLayout()); + myComponent.putClientProperty(DslComponentProperty.VERTICAL_COMPONENT_GAP, VerticalComponentGap.BOTH); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + constraints.weightx = 1; + myComponent.add(new JLabel(JavaBundle.message("nullable.notnull.annotation.used.label")), constraints); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets.bottom = 3; + constraints.gridy = 1; + myComponent.add(myCombo, constraints); + constraints.insets.bottom = 0; + constraints.gridy = 2; + constraints.fill = GridBagConstraints.BOTH; + constraints.weighty = 1; + myComponent.add(tablePanel, constraints); + } + + private @NotNull TableCellRenderer createHeaderRenderer() { + TableCellRenderer defaultRenderer = myTable.getTableHeader().getDefaultRenderer(); + + TableCellRenderer headerRenderer = new TableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column) { + Component component = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (component instanceof JComponent) { + ((JComponent)component) + .setToolTipText(column == 1 ? JavaBundle.message("nullable.notnull.annotations.runtime.instrumentation.tooltip") : null); + } + return component; + } + }; + return headerRenderer; + } + + private void loadAdvancedAnnotations(@NotNull AnnotationPanelModel model) { + // No project-specific annotations are possible for default project + if (myProject.isDefault()) return; + String loading = JavaBundle.message("loading.additional.annotations"); + myCombo.addItem(loading); + DumbService.getInstance(myProject).runWhenSmart(() -> { + ReadAction.nonBlocking(model::getAdvancedAnnotations) + .finishOnUiThread(ModalityState.any(), advancedAnnotations -> { + myCombo.removeItem(loading); + int count = myCombo.getItemCount(); + Object selectedItem = myCombo.getSelectedItem(); + List newItems = Stream.concat( + IntStream.range(0, count).mapToObj(myCombo::getItemAt), + advancedAnnotations.stream()).distinct().toList(); + myCombo.removeAllItems(); + newItems.forEach(myCombo::addItem); + myCombo.setSelectedItem(selectedItem); + }).submit(AppExecutorUtil.getAppExecutorService()); + }); + myCombo.addActionListener(new ActionListener() { + Object previous = myCombo.getSelectedItem(); + + @Override + public void actionPerformed(ActionEvent e) { + Object item = myCombo.getSelectedItem(); + if (item == loading) { + myCombo.setSelectedItem(previous); + } else { + previous = item; + } + } + }); + } + + private void addRow(String annotation, boolean checked) { + myTableModel.addRow(new Object[]{annotation, checked}); + } + + private Integer selectAnnotation(String annotation) { + for (int i = 0; i < myTable.getRowCount(); i++) { + if (annotation.equals(myTable.getValueAt(i, 0))) { + myTable.setRowSelectionInterval(i, i); + return i; + } + } + return null; + } + + private @NlsSafe String getSelectedAnnotation() { + int selectedRow = myTable.getSelectedRow(); + return selectedRow < 0 ? null : (String)myTable.getValueAt(selectedRow, 0); + } + + private void chooseAnnotation(@NlsSafe String title) { + final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject) + .createNoInnerClassesScopeChooser(JavaBundle.message("dialog.title.choose.annotation", title), GlobalSearchScope.allScope(myProject), new ClassFilter() { + @Override + public boolean isAccepted(PsiClass aClass) { + if (!aClass.isAnnotationType()) return false; + return true; + } + }, null); + chooser.showDialog(); + final PsiClass selected = chooser.getSelected(); + if (selected == null) { + return; + } + final String qualifiedName = selected.getQualifiedName(); + if (selectAnnotation(qualifiedName) == null) { + addRow(qualifiedName, false); + addAnnotationToCombo(qualifiedName); + Object added = selectAnnotation(qualifiedName); + assert added != null; + myTable.scrollRectToVisible(myTable.getCellRect((int)added, 0, true)); + } + } + + private void addAnnotationToCombo(@NlsSafe String annotation) { + int insertAt = 0; + for (; insertAt < myCombo.getItemCount(); insertAt += 1) { + if (myCombo.getItemAt(insertAt).compareTo(annotation) >= 0) break; + } + myCombo.insertItemAt(annotation, insertAt); + } + + public JComponent getComponent() { + return myComponent; + } + + String getDefaultAnnotation() { + return myCombo.getItem(); + } + + public String[] getAnnotations() { + int size = myTable.getRowCount(); + String[] result = new String[size]; + for (int i = 0; i < size; i++) { + result[i] = (String)myTable.getValueAt(i, 0); + } + return result; + } + + List getCheckedAnnotations() { + List result = new ArrayList<>(); + for (int i = 0; i < myTable.getRowCount(); i++) { + if (Boolean.TRUE.equals(myTable.getValueAt(i, 1))) { + result.add((String)myTable.getValueAt(i, 0)); + } + } + return result; + } +} \ No newline at end of file diff --git a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java index 2b33e20ec342..d9aae14f6c27 100644 --- a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java +++ b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java @@ -14,8 +14,8 @@ import java.awt.event.ActionListener; public class NullableNotNullDialog extends DialogWrapper { private final Project myProject; - private final AnnotationsPanel myNullablePanel; - private final AnnotationsPanel myNotNullPanel; + private final NullableAnnotationsPanel myNullablePanel; + private final NullableAnnotationsPanel myNotNullPanel; private final boolean myShowInstrumentationOptions; public static final @NlsSafe String NULLABLE = "Nullable"; public static final @NlsSafe String NOT_NULL = "NotNull"; @@ -30,12 +30,12 @@ public class NullableNotNullDialog extends DialogWrapper { myShowInstrumentationOptions = showInstrumentationOptions; NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); - myNullablePanel = new AnnotationsPanel(project, + myNullablePanel = new NullableAnnotationsPanel(project, new NullabilityAnnotationPanelModel.NullableModel(manager), - false, true); - myNotNullPanel = new AnnotationsPanel(project, + false); + myNotNullPanel = new NullableAnnotationsPanel(project, new NullabilityAnnotationPanelModel.NotNullModel(manager), - showInstrumentationOptions, true); + showInstrumentationOptions); init(); setTitle(JavaBundle.message("nullable.notnull.configuration.dialog.title")); diff --git a/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt b/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt index 41d1a6209cc6..92218736321e 100644 --- a/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt +++ b/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt @@ -50,9 +50,8 @@ private class JavaCoverageOptions(private val project: Project) : UiDslUnnamedCo } row { val excludeAnnotationsPanel = object : AnnotationsPanel( - project, "Exclude", "", - coverageOptionsProvider.excludeAnnotationPatterns, - JavaCoverageOptionsProvider.defaultExcludeAnnotationPatterns, emptySet(), false, false + project, "Exclude", coverageOptionsProvider.excludeAnnotationPatterns, + JavaCoverageOptionsProvider.defaultExcludeAnnotationPatterns ) { override fun isAnnotationAccepted(annotation: PsiClass): Boolean { return annotation.containingClass == null