2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInspection.nullable;
18 import com.intellij.codeInsight.AnnotationUtil;
19 import com.intellij.codeInsight.NullableNotNullManager;
20 import com.intellij.codeInsight.daemon.GroupNames;
21 import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
22 import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
23 import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix;
24 import com.intellij.codeInspection.*;
25 import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.WriteExternalException;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.psi.*;
31 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
32 import com.intellij.psi.codeStyle.VariableKind;
33 import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
34 import com.intellij.psi.search.GlobalSearchScope;
35 import com.intellij.psi.search.searches.OverridingMethodsSearch;
36 import com.intellij.psi.util.*;
37 import com.intellij.util.ArrayUtil;
38 import com.intellij.util.containers.ContainerUtil;
39 import org.jdom.Element;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
44 import java.util.ArrayList;
45 import java.util.List;
48 import static com.intellij.patterns.PsiJavaPatterns.psiElement;
49 import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
51 public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionTool {
52 // deprecated fields remain to minimize changes to users inspection profiles (which are often located in version control).
53 @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL = true;
54 @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL = true;
55 @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE = true;
56 @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL = true;
57 @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_GETTER = true;
58 @SuppressWarnings({"WeakerAccess"}) public boolean IGNORE_EXTERNAL_SUPER_NOTNULL;
59 @SuppressWarnings({"WeakerAccess"}) public boolean REQUIRE_NOTNULL_FIELDS_INITIALIZED = true;
60 @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED;
61 @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true;
62 @Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true; // remains for test
63 @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD = true;
64 public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true;
66 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.nullable.NullableStuffInspectionBase");
69 public void writeSettings(@NotNull Element node) throws WriteExternalException {
70 super.writeSettings(node);
71 for (Element child : new ArrayList<>(node.getChildren())) {
72 String name = child.getAttributeValue("name");
73 String value = child.getAttributeValue("value");
74 if ("IGNORE_EXTERNAL_SUPER_NOTNULL".equals(name) && "false".equals(value) ||
75 "REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED".equals(name) && "false".equals(value) ||
76 "REQUIRE_NOTNULL_FIELDS_INITIALIZED".equals(name) && "true".equals(value) ||
77 "REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER".equals(name) && "true".equals(value)) {
78 node.removeContent(child);
85 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
86 final PsiFile file = holder.getFile();
87 if (!PsiUtil.isLanguageLevel5OrHigher(file) || nullabilityAnnotationsNotAvailable(file)) {
88 return new PsiElementVisitor() { };
90 return new JavaElementVisitor() {
92 public void visitMethod(PsiMethod method) {
93 checkNullableStuffForMethod(method, holder, isOnTheFly);
97 public void visitField(PsiField field) {
98 final PsiType type = field.getType();
99 final Annotated annotated = check(field, holder, type);
100 if (TypeConversionUtil.isPrimitiveAndNotNull(type)) {
103 Project project = holder.getProject();
104 final NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
105 if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) {
106 final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable();
107 final List<String> annoToRemove = annotated.isDeclaredNotNull ? manager.getNullables() : manager.getNotNulls();
109 if (!checkNonStandardAnnotations(field, annotated, manager, anno, holder)) return;
111 checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder);
113 checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder);
116 if (REQUIRE_NOTNULL_FIELDS_INITIALIZED && !annotated.isDeclaredNullable) {
117 checkNotNullFieldsInitialized(field, manager, holder);
122 public void visitParameter(PsiParameter parameter) {
123 check(parameter, holder, parameter.getType());
127 public void visitAnnotation(PsiAnnotation annotation) {
128 if (!AnnotationUtil.NOT_NULL.equals(annotation.getQualifiedName())) return;
130 PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception");
131 if (value instanceof PsiClassObjectAccessExpression) {
132 PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(((PsiClassObjectAccessExpression)value).getOperand().getType());
133 if (psiClass != null && !hasStringConstructor(psiClass)) {
134 //noinspection DialogTitleCapitalization
135 holder.registerProblem(value,
136 "Custom exception class should have a constructor with a single message parameter of String type",
137 ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
143 private boolean hasStringConstructor(PsiClass aClass) {
144 for (PsiMethod method : aClass.getConstructors()) {
145 PsiParameterList list = method.getParameterList();
146 if (list.getParametersCount() == 1 &&
147 list.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
156 protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) {
160 private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) {
161 final Project project = file.getProject();
162 final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
163 final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
164 return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null;
167 private static boolean checkNonStandardAnnotations(PsiField field,
169 NullableNotNullManager manager, String anno, @NotNull ProblemsHolder holder) {
170 if (!AnnotationUtil.isAnnotatingApplicable(field, anno)) {
171 final PsiAnnotation notNull = AnnotationUtil.findAnnotation(field, manager.getNotNulls());
172 final PsiAnnotation nullable = AnnotationUtil.findAnnotation(field, manager.getNullables());
173 final PsiAnnotation annotation;
174 String message = "Not \'";
175 if (annotated.isDeclaredNullable) {
176 message += nullable.getQualifiedName();
177 annotation = nullable;
179 message += notNull.getQualifiedName();
180 annotation = notNull;
182 message += "\' but \'" + anno + "\' would be used for code generation.";
183 final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement();
184 holder.registerProblem(annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() ? annotationNameReferenceElement : field.getNameIdentifier(),
186 ProblemHighlightType.WEAK_WARNING,
187 new ChangeNullableDefaultsFix(notNull, nullable, manager));
193 private void checkAccessors(PsiField field,
196 NullableNotNullManager manager, final String anno, final List<String> annoToRemove, @NotNull ProblemsHolder holder) {
197 String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD);
198 final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
199 final PsiMethod getter = PropertyUtil.findPropertyGetter(field.getContainingClass(), propName, isStatic, false);
200 final PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier();
201 if (nameIdentifier != null && nameIdentifier.isPhysical()) {
202 if (PropertyUtil.isSimpleGetter(getter)) {
203 AnnotateMethodFix getterAnnoFix = new AnnotateMethodFix(anno, ArrayUtil.toStringArray(annoToRemove)) {
205 public int shouldAnnotateBaseMethod(PsiMethod method, PsiMethod superMethod, Project project) {
209 if (REPORT_NOT_ANNOTATED_GETTER) {
210 if (!manager.hasNullability(getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) {
211 holder.registerProblem(nameIdentifier, InspectionsBundle
212 .message("inspection.nullable.problems.annotated.field.getter.not.annotated", getPresentableAnnoName(field)),
213 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, getterAnnoFix);
216 if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) ||
217 annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) {
218 holder.registerProblem(nameIdentifier, InspectionsBundle.message(
219 "inspection.nullable.problems.annotated.field.getter.conflict", getPresentableAnnoName(field), getPresentableAnnoName(getter)),
220 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, getterAnnoFix);
225 final PsiClass containingClass = field.getContainingClass();
226 final PsiMethod setter = PropertyUtil.findPropertySetter(containingClass, propName, isStatic, false);
227 if (setter != null && setter.isPhysical()) {
228 final PsiParameter[] parameters = setter.getParameterList().getParameters();
229 assert parameters.length == 1 : setter.getText();
230 final PsiParameter parameter = parameters[0];
231 LOG.assertTrue(parameter != null, setter.getText());
232 AddAnnotationPsiFix addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter);
233 if (REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) {
234 final PsiIdentifier nameIdentifier1 = parameter.getNameIdentifier();
235 assertValidElement(setter, parameter, nameIdentifier1);
236 holder.registerProblem(nameIdentifier1,
237 InspectionsBundle.message("inspection.nullable.problems.annotated.field.setter.parameter.not.annotated",
238 getPresentableAnnoName(field)),
239 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
242 if (PropertyUtil.isSimpleSetter(setter)) {
243 if (annotated.isDeclaredNotNull && isNullableNotInferred(parameter, false)) {
244 final PsiIdentifier nameIdentifier1 = parameter.getNameIdentifier();
245 assertValidElement(setter, parameter, nameIdentifier1);
246 holder.registerProblem(nameIdentifier1, InspectionsBundle.message(
247 "inspection.nullable.problems.annotated.field.setter.parameter.conflict",
248 getPresentableAnnoName(field), getPresentableAnnoName(parameter)),
249 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
257 private static AddAnnotationPsiFix createAddAnnotationFix(String anno, List<String> annoToRemove, PsiParameter parameter) {
258 return new AddAnnotationPsiFix(anno, parameter, PsiNameValuePair.EMPTY_ARRAY, ArrayUtil.toStringArray(annoToRemove));
261 private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) {
262 LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText());
263 LOG.assertTrue(parameter.isPhysical(), setter.getText());
266 private static void checkNotNullFieldsInitialized(PsiField field, NullableNotNullManager manager, @NotNull ProblemsHolder holder) {
267 PsiAnnotation annotation = manager.getNotNullAnnotation(field, false);
268 if (annotation == null || HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) return;
270 boolean byDefault = manager.isContainerAnnotation(annotation);
271 PsiJavaCodeReferenceElement name = annotation.getNameReferenceElement();
272 holder.registerProblem(annotation.isPhysical() && !byDefault ? annotation : field.getNameIdentifier(),
273 (byDefault && name != null ? "@" + name.getReferenceName() : "Not-null") + " fields must be initialized");
276 private void checkConstructorParameters(PsiField field,
278 NullableNotNullManager manager,
279 String anno, List<String> annoToRemove, @NotNull ProblemsHolder holder) {
280 List<PsiExpression> initializers = DfaPsiUtil.findAllConstructorInitializers(field);
281 if (initializers.isEmpty()) return;
283 List<PsiParameter> notNullParams = ContainerUtil.newArrayList();
285 boolean isFinal = field.hasModifierProperty(PsiModifier.FINAL);
287 for (PsiExpression rhs : initializers) {
288 if (rhs instanceof PsiReferenceExpression) {
289 PsiElement target = ((PsiReferenceExpression)rhs).resolve();
290 if (isConstructorParameter(target) && target.isPhysical()) {
291 PsiParameter parameter = (PsiParameter)target;
292 if (REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) {
293 final PsiIdentifier nameIdentifier = parameter.getNameIdentifier();
294 if (nameIdentifier != null && nameIdentifier.isPhysical()) {
295 holder.registerProblem(
297 InspectionsBundle.message("inspection.nullable.problems.annotated.field.constructor.parameter.not.annotated", getPresentableAnnoName(field)),
298 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createAddAnnotationFix(anno, annoToRemove, parameter));
303 if (isFinal && annotated.isDeclaredNullable && isNotNullNotInferred(parameter, false, false)) {
304 notNullParams.add(parameter);
311 if (notNullParams.size() != initializers.size()) {
312 // it's not the case that the field is final and @Nullable and always initialized via @NotNull parameters
313 // so there might be other initializers that could justify it being nullable
314 // so don't highlight field and constructor parameter annotation inconsistency
318 PsiIdentifier nameIdentifier = field.getNameIdentifier();
319 if (nameIdentifier.isPhysical()) {
320 holder.registerProblem(nameIdentifier, "@" + getPresentableAnnoName(field) + " field is always initialized not-null",
321 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new AddNotNullAnnotationFix(field));
325 private static boolean isConstructorParameter(@Nullable PsiElement parameter) {
326 return parameter instanceof PsiParameter && psiElement(PsiParameterList.class).withParent(psiMethod().constructor(true)).accepts(parameter.getParent());
330 private static String getPresentableAnnoName(@NotNull PsiModifierListOwner owner) {
331 NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
332 Set<String> names = ContainerUtil.newHashSet(manager.getNullables());
333 names.addAll(manager.getNotNulls());
335 PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(owner, names);
336 if (annotation != null) return getPresentableAnnoName(annotation);
338 String anno = manager.getNotNull(owner);
339 return StringUtil.getShortName(anno != null ? anno : StringUtil.notNullize(manager.getNullable(owner), "???"));
342 public static String getPresentableAnnoName(@NotNull PsiAnnotation annotation) {
343 return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???"));
346 private static class Annotated {
347 private final boolean isDeclaredNotNull;
348 private final boolean isDeclaredNullable;
350 private Annotated(final boolean isDeclaredNotNull, final boolean isDeclaredNullable) {
351 this.isDeclaredNotNull = isDeclaredNotNull;
352 this.isDeclaredNullable = isDeclaredNullable;
355 private static Annotated check(final PsiModifierListOwner parameter, final ProblemsHolder holder, PsiType type) {
356 final NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject());
357 PsiAnnotation isDeclaredNotNull = AnnotationUtil.findAnnotation(parameter, manager.getNotNulls());
358 PsiAnnotation isDeclaredNullable = AnnotationUtil.findAnnotation(parameter, manager.getNullables());
359 if (isDeclaredNullable != null && isDeclaredNotNull != null) {
360 reportNullableNotNullConflict(holder, parameter, isDeclaredNullable, isDeclaredNotNull);
362 if ((isDeclaredNotNull != null || isDeclaredNullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) {
363 PsiAnnotation annotation = isDeclaredNotNull == null ? isDeclaredNullable : isDeclaredNotNull;
364 reportPrimitiveType(holder, annotation, annotation, parameter);
366 return new Annotated(isDeclaredNotNull != null,isDeclaredNullable != null);
369 private static void reportPrimitiveType(final ProblemsHolder holder, final PsiElement psiElement, final PsiAnnotation annotation,
370 final PsiModifierListOwner listOwner) {
371 holder.registerProblem(psiElement.isPhysical() ? psiElement : listOwner.getNavigationElement(),
372 InspectionsBundle.message("inspection.nullable.problems.primitive.type.annotation"),
373 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(annotation, listOwner));
378 public String getDisplayName() {
379 return InspectionsBundle.message("inspection.nullable.problems.display.name");
384 public String getGroupDisplayName() {
385 return GroupNames.BUGS_GROUP_NAME;
390 public String getShortName() {
391 return "NullableProblems";
394 private void checkNullableStuffForMethod(PsiMethod method, final ProblemsHolder holder, boolean isOnFly) {
395 Annotated annotated = check(method, holder, method.getReturnType());
397 List<PsiMethod> superMethods = ContainerUtil.map(
398 method.findSuperMethodSignaturesIncludingStatic(true), signature -> signature.getMethod());
400 final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject());
402 checkSupers(method, holder, annotated, superMethods, nullableManager);
403 checkParameters(method, holder, superMethods, nullableManager, isOnFly);
404 checkOverriders(method, holder, annotated, nullableManager);
407 private void checkSupers(PsiMethod method,
408 ProblemsHolder holder,
410 List<PsiMethod> superMethods, NullableNotNullManager nullableManager) {
411 if (REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE) {
412 for (PsiMethod superMethod : superMethods) {
413 if (annotated.isDeclaredNullable && isNotNullNotInferred(superMethod, true, false)) {
414 final PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNullables(), true);
415 holder.registerProblem(annotation != null ? annotation : method.getNameIdentifier(),
416 InspectionsBundle.message("inspection.nullable.problems.Nullable.method.overrides.NotNull",
417 getPresentableAnnoName(method), getPresentableAnnoName(superMethod)),
418 ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
424 if (REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL) {
425 for (PsiMethod superMethod : superMethods) {
426 if (!nullableManager.hasNullability(method) && isNotNullNotInferred(superMethod, true, IGNORE_EXTERNAL_SUPER_NOTNULL)) {
427 final String defaultNotNull = nullableManager.getDefaultNotNull();
428 final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables());
429 final LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull)
430 ? createAnnotateMethodFix(defaultNotNull, annotationsToRemove)
431 : createChangeDefaultNotNullFix(nullableManager, superMethod);
432 holder.registerProblem(method.getNameIdentifier(),
433 InspectionsBundle.message("inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)),
434 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
442 private void checkParameters(PsiMethod method,
443 ProblemsHolder holder,
444 List<PsiMethod> superMethods,
445 NullableNotNullManager nullableManager,
447 PsiParameter[] parameters = method.getParameterList().getParameters();
448 for (int i = 0; i < parameters.length; i++) {
449 PsiParameter parameter = parameters[i];
451 List<PsiParameter> superParameters = ContainerUtil.newArrayList();
452 for (PsiMethod superMethod : superMethods) {
453 PsiParameter[] _superParameters = superMethod.getParameterList().getParameters();
454 if (_superParameters.length == parameters.length) {
455 superParameters.add(_superParameters[i]);
459 if (REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE) {
460 for (PsiParameter superParameter : superParameters) {
461 if (isNotNullNotInferred(parameter, false, false) &&
462 isNullableNotInferred(superParameter, false)) {
463 final PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameter, nullableManager.getNotNulls(), true);
464 holder.registerProblem(annotation != null ? annotation : parameter.getNameIdentifier(),
465 InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.Nullable",
466 getPresentableAnnoName(parameter),
467 getPresentableAnnoName(superParameter)),
468 ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
473 if (REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL) {
474 for (PsiParameter superParameter : superParameters) {
475 if (!nullableManager.hasNullability(parameter) && isNotNullNotInferred(superParameter, false, IGNORE_EXTERNAL_SUPER_NOTNULL)) {
476 final LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull())
477 ? new AddNotNullAnnotationFix(parameter)
478 : createChangeDefaultNotNullFix(nullableManager, superParameter);
479 holder.registerProblem(parameter.getNameIdentifier(),
480 InspectionsBundle.message("inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(superParameter)),
481 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
487 if (REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) {
488 for (PsiParameter superParameter : superParameters) {
489 if (!nullableManager.hasNullability(superParameter) && isNotNullNotInferred(parameter, false, false)) {
490 PsiAnnotation notNullAnnotation = nullableManager.getNotNullAnnotation(parameter, false);
491 assert notNullAnnotation != null;
492 boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
493 final LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null;
494 holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(),
495 InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.not.annotated", getPresentableAnnoName(parameter)),
496 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
503 checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, isOnFly, i, parameter);
507 private void checkNullLiteralArgumentOfNotNullParameterUsages(PsiMethod method,
508 ProblemsHolder holder,
509 NullableNotNullManager nullableManager,
512 PsiParameter parameter) {
513 if (REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER && isOnFly && isNotNullNotInferred(parameter, false, false)) {
514 PsiAnnotation notNullAnnotation = nullableManager.getNotNullAnnotation(parameter, false);
515 if (JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) {
516 boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
517 holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(),
518 InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.receives.null.literal", getPresentableAnnoName(parameter)),
519 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
520 createNavigateToNullParameterUsagesFix(parameter));
525 private void checkOverriders(PsiMethod method,
526 ProblemsHolder holder,
528 NullableNotNullManager nullableManager) {
529 PsiParameter[] parameters = method.getParameterList().getParameters();
530 if (REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) {
531 boolean[] parameterAnnotated = new boolean[parameters.length];
532 boolean[] parameterQuickFixSuggested = new boolean[parameters.length];
533 boolean hasAnnotatedParameter = false;
534 for (int i = 0; i < parameters.length; i++) {
535 PsiParameter parameter = parameters[i];
536 parameterAnnotated[i] = isNotNullNotInferred(parameter, false, false);
537 hasAnnotatedParameter |= parameterAnnotated[i];
539 if (hasAnnotatedParameter || annotated.isDeclaredNotNull) {
540 PsiManager manager = method.getManager();
541 final String defaultNotNull = nullableManager.getDefaultNotNull();
542 final boolean superMethodApplicable = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull);
543 PsiMethod[] overridings =
544 OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY);
545 boolean methodQuickFixSuggested = false;
546 for (PsiMethod overriding : overridings) {
547 if (!manager.isInProject(overriding)) continue;
549 final boolean applicable = AnnotationUtil.isAnnotatingApplicable(overriding, defaultNotNull);
550 if (!methodQuickFixSuggested
551 && annotated.isDeclaredNotNull
552 && !isNotNullNotInferred(overriding, false, false)
553 && (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true))) {
554 method.getNameIdentifier(); //load tree
555 PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNotNulls());
556 final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables());
558 final LocalQuickFix fix;
560 fix = new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove);
563 fix = superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method);
566 PsiElement psiElement = annotation;
567 if (!annotation.isPhysical()) {
568 psiElement = method.getNameIdentifier();
569 if (psiElement == null) continue;
571 holder.registerProblem(psiElement, InspectionsBundle.message("nullable.stuff.problems.overridden.methods.are.not.annotated"),
572 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
574 methodQuickFixSuggested = true;
576 if (hasAnnotatedParameter) {
577 PsiParameter[] psiParameters = overriding.getParameterList().getParameters();
578 for (int i = 0; i < psiParameters.length; i++) {
579 if (parameterQuickFixSuggested[i]) continue;
580 PsiParameter parameter = psiParameters[i];
581 if (parameterAnnotated[i] && !isNotNullNotInferred(parameter, false, false) && !isNullableNotInferred(parameter, false)) {
582 parameters[i].getNameIdentifier(); //be sure that corresponding tree element available
583 PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameters[i], nullableManager.getNotNulls());
584 PsiElement psiElement = annotation;
585 if (annotation == null || !annotation.isPhysical()) {
586 psiElement = parameters[i].getNameIdentifier();
587 if (psiElement == null) continue;
589 holder.registerProblem(psiElement,
590 InspectionsBundle.message("nullable.stuff.problems.overridden.method.parameters.are.not.annotated"),
591 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
593 ? createChangeDefaultNotNullFix(nullableManager, parameters[i])
594 : new AnnotateOverriddenMethodParameterFix(defaultNotNull,
595 nullableManager.getDefaultNullable()));
596 parameterQuickFixSuggested[i] = true;
605 private static boolean isNotNullNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) {
606 Project project = owner.getProject();
607 NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
608 if (!manager.isNotNull(owner, checkBases)) return false;
610 PsiAnnotation anno = manager.getNotNullAnnotation(owner, checkBases);
611 if (anno == null || AnnotationUtil.isInferredAnnotation(anno)) return false;
612 if (skipExternal && AnnotationUtil.isExternalAnnotation(anno)) return false;
616 public static boolean isNullableNotInferred(@NotNull PsiModifierListOwner owner, boolean checkBases) {
617 Project project = owner.getProject();
618 NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
619 if (!manager.isNullable(owner, checkBases)) return false;
621 PsiAnnotation anno = manager.getNullableAnnotation(owner, checkBases);
622 return !(anno != null && AnnotationUtil.isInferredAnnotation(anno));
625 private static LocalQuickFix createChangeDefaultNotNullFix(NullableNotNullManager nullableManager, PsiModifierListOwner modifierListOwner) {
626 final PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, nullableManager.getNotNulls());
627 if (annotation != null) {
628 final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
629 if (referenceElement != null && referenceElement.resolve() != null) {
630 return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null, nullableManager);
636 protected AnnotateMethodFix createAnnotateMethodFix(final String defaultNotNull, final String[] annotationsToRemove) {
637 return new AnnotateMethodFix(defaultNotNull, annotationsToRemove);
640 private static void reportNullableNotNullConflict(final ProblemsHolder holder, final PsiModifierListOwner listOwner, final PsiAnnotation declaredNullable,
641 final PsiAnnotation declaredNotNull) {
642 final String bothNullableNotNullMessage = InspectionsBundle.message("inspection.nullable.problems.Nullable.NotNull.conflict",
643 getPresentableAnnoName(declaredNullable),
644 getPresentableAnnoName(declaredNotNull));
645 holder.registerProblem(declaredNotNull.isPhysical() ? declaredNotNull : listOwner.getNavigationElement(),
646 bothNullableNotNullMessage,
647 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(declaredNotNull, listOwner));
648 holder.registerProblem(declaredNullable.isPhysical() ? declaredNullable : listOwner.getNavigationElement(),
649 bothNullableNotNullMessage,
650 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(declaredNullable, listOwner));
654 public JComponent createOptionsPanel() {
655 throw new RuntimeException("No UI in headless mode");
658 private static class MyAnnotateMethodFix extends AnnotateMethodFix {
659 public MyAnnotateMethodFix(String defaultNotNull, String[] annotationsToRemove) {
660 super(defaultNotNull, annotationsToRemove);
664 protected boolean annotateOverriddenMethods() {
669 public int shouldAnnotateBaseMethod(PsiMethod method, PsiMethod superMethod, Project project) {
675 public String getName() {
676 return InspectionsBundle.message("annotate.overridden.methods.as.notnull", ClassUtil.extractClassName(myAnnotation));