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 045bf2acfa2a..f5acfcfde8e5 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java @@ -3,6 +3,7 @@ package com.intellij.codeInsight; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.util.*; import com.intellij.util.*; @@ -280,6 +281,28 @@ public class AnnotationUtil { public static final int CHECK_INFERRED = 0x04; public static final int CHECK_TYPE = 0x08; + /** + * @param type type to check + * @param qualifiedNames annotation qualified names + * @return found type annotation, or null if not found. For type parameter types upper bound annotations are also checked + */ + @Contract("null, _ -> null") + public static @Nullable PsiAnnotation findTypeAnnotationInHierarchy(@Nullable PsiType type, @NotNull Set qualifiedNames) { + if (type == null) return null; + Ref result = Ref.create(null); + InheritanceUtil.processSuperTypes(type, true, eachType -> { + for (PsiAnnotation annotation : eachType.getAnnotations()) { + String qualifiedName = annotation.getQualifiedName(); + if (qualifiedNames.contains(qualifiedName)) { + result.set(annotation); + return false; + } + } + return !(eachType instanceof PsiClassType) || PsiUtil.resolveClassInClassTypeOnly(eachType) instanceof PsiTypeParameter; + }); + return result.get(); + } + @MagicConstant(flags = {CHECK_HIERARCHY, CHECK_EXTERNAL, CHECK_INFERRED, CHECK_TYPE}) @Target({ElementType.PARAMETER, ElementType.METHOD}) private @interface Flags { } 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 e8c3ed675c7e..9c024eda429c 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -5,7 +5,6 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.RecursionManager; -import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.util.*; import org.jetbrains.annotations.ApiStatus; @@ -305,21 +304,8 @@ public abstract class NullableNotNullManager { if (annotation != memberAnno && !qualifiedNames.contains(annotation.getQualifiedName())) return null; return annotation; } - if (type != null && !(type instanceof PsiPrimitiveType)) { - Ref result = Ref.create(null); - InheritanceUtil.processSuperTypes(type, true, eachType -> { - for (PsiAnnotation annotation : eachType.getAnnotations()) { - String qualifiedName = annotation.getQualifiedName(); - if (qualifiedNames.contains(qualifiedName)) { - result.set(annotation); - return false; - } - } - return !(type instanceof PsiClassType) || PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter; - }); - return result.get(); - } - return null; + if (type instanceof PsiPrimitiveType) return null; + return findTypeAnnotationInHierarchy(type, qualifiedNames); } private static @NotNull PsiAnnotation preferTypeAnnotation(@NotNull PsiAnnotation memberAnno, @Nullable PsiType type) { diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java index ca0d336eed8f..5cc3e8645345 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java @@ -860,8 +860,9 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen private static boolean isReturnedFromAnnotatedMethod(final ULiteralExpression expression, final String fqn, - @Nullable final Set nonNlsTargets) { + final @Nullable Set nonNlsTargets) { PsiMethod method; + PsiType returnType = null; UNamedExpression nameValuePair = UastUtils.getParentOfType(expression, UNamedExpression.class); if (nameValuePair != null) { method = UastUtils.getAnnotationMethod(nameValuePair); @@ -873,12 +874,22 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen ((UCallExpression)parent).getKind() == UastCallKind.NEW_ARRAY_WITH_INITIALIZER) { parent = parent.getUastParent(); } + if (parent == null) return false; final UElement returnStmt = UastUtils.getParentOfType(parent, UReturnExpression.class, false, UCallExpression.class, ULambdaExpression.class); if (!(returnStmt instanceof UReturnExpression)) { return false; } - UMethod uMethod = UastUtils.getParentOfType(expression, UMethod.class); - method = uMethod != null ? uMethod.getJavaPsi() : null; + UElement jumpTarget = ((UReturnExpression)returnStmt).getJumpTarget(); + if (jumpTarget instanceof UMethod) { + method = ((UMethod)jumpTarget).getJavaPsi(); + } + else if (jumpTarget instanceof ULambdaExpression) { + PsiType type = ((ULambdaExpression)jumpTarget).getFunctionalInterfaceType(); + returnType = LambdaUtil.getFunctionalInterfaceReturnType(type); + if (type == null) return false; + method = LambdaUtil.getFunctionalInterfaceMethod(type); + } + else return false; } if (method == null) return false; @@ -891,6 +902,14 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen if (AnnotationUtil.isAnnotated(method, fqn, CHECK_HIERARCHY | CHECK_EXTERNAL)) { return true; } + + if (returnType != null) { + PsiAnnotation typeAnnotation = AnnotationUtil.findTypeAnnotationInHierarchy(returnType, NON_NLS_NAMES); + if (typeAnnotation != null) { + return typeAnnotation.hasQualifiedName(fqn); + } + } + if (nonNlsTargets != null) { nonNlsTargets.add(method); } diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java index c80abe93a9ca..9d05bb8a29d1 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java @@ -128,8 +128,23 @@ public class JavaI18nUtil extends I18nUtil { if (!idx.isPresent()) return false; PsiMethod method = callExpression.resolve(); - return method != null && isMethodParameterAnnotatedWith(method, idx.getAsInt(), null, annFqn, null, nonNlsTargets); - + if (method == null) return false; + if (isMethodParameterAnnotatedWith(method, idx.getAsInt(), null, annFqn, null, nonNlsTargets)) { + return true; + } + PsiParameter parameter = method.getParameterList().getParameter(idx.getAsInt()); + if (parameter != null) { + PsiType parameterType = parameter.getType(); + PsiType receiverType = callExpression.getReceiverType(); + if (receiverType instanceof PsiClassType) { + PsiClassType.ClassResolveResult result = ((PsiClassType)receiverType).resolveGenerics(); + parameterType = result.getSubstitutor().substitute(parameterType); + } + if (AnnotationUtil.findTypeAnnotationInHierarchy(parameterType, Collections.singleton(annFqn)) != null) { + return true; + } + } + return false; } @NotNull diff --git a/plugins/java-i18n/testData/inspections/i18n/NlsTypeUse.java b/plugins/java-i18n/testData/inspections/i18n/NlsTypeUse.java new file mode 100644 index 000000000000..6d951e187087 --- /dev/null +++ b/plugins/java-i18n/testData/inspections/i18n/NlsTypeUse.java @@ -0,0 +1,33 @@ +package org.jetbrains.annotations; + +import java.lang.annotation.*; +import java.util.*; +import java.util.function.*; +import static java.lang.annotation.ElementType.*; + +@Retention(RetentionPolicy.CLASS) +@Target({METHOD, FIELD, PARAMETER, LOCAL_VARIABLE, TYPE_USE, TYPE, PACKAGE}) +@interface Nls {} + +interface AnnotatedSam { + @Nls String getString(); +} + +class NlsTypeUse { + native void foo(AnnotatedSam str); + native void foo2(Supplier<@Nls String> str); + native void foo3(Supplier str); + + Supplier<@Nls String> getSupplier() { + return () -> "foo"; + } + + void test(Map map, BiConsumer<@Nls String, String> cons) { + foo(() -> "bar"); + foo2(() -> "bar"); + foo3(() -> "bar"); + + map.put("foo", "bar"); + cons.accept("foo", "bar"); + } +} \ No newline at end of file diff --git a/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java b/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java index b00954dc9733..42d3e5d321ce 100644 --- a/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java +++ b/plugins/java-i18n/testSrc/com/intellij/codeInspection/i18n/I18NInspectionTest.java @@ -96,6 +96,16 @@ public class I18NInspectionTest extends LightJavaCodeInsightFixtureTestCase { myTool.setIgnoreForEnumConstants(oldState); } } + + public void testNlsTypeUse() { + boolean old = myTool.setIgnoreForAllButNls(true); + try { + doTest(); + } + finally { + myTool.setIgnoreForAllButNls(old); + } + } @Override protected String getTestDataPath() { diff --git a/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt b/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt index 8ce17c0008e7..60b5941b5fa8 100644 --- a/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt +++ b/uast/uast-common/src/org/jetbrains/uast/UastUtils.kt @@ -50,6 +50,7 @@ fun UElement.skipParentOfType(strict: Boolean, vararg parentClasses: Class UElement.getParentOfType( parentClass: Class, strict: Boolean = true,