support @TypeQualifierNickname usages without its declaration (IDEA-182842)

This commit is contained in:
peter
2018-04-25 09:40:33 +02:00
parent 7651f1bc51
commit 0c5937c8b7
3 changed files with 49 additions and 18 deletions
@@ -9,7 +9,9 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.DefaultJDOMExternalizer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
@@ -21,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.serialization.java.compiler.JpsJavaCompilerNotNullableSerializer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -87,17 +90,39 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement
return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> {
List<PsiClass> result = new ArrayList<>();
GlobalSearchScope scope = GlobalSearchScope.allScope(myProject);
for (PsiClass tqNick : JavaPsiFacade.getInstance(myProject).findClasses(TYPE_QUALIFIER_NICKNAME, scope)) {
result.addAll(ContainerUtil.findAll(MetaAnnotationUtil.getChildren(tqNick, scope), candidate -> {
String qname = candidate.getQualifiedName();
if (qname == null || qname.startsWith("javax.annotation.")) return false;
return getNickNamedNullability(candidate) != Nullness.UNKNOWN;
}));
PsiClass[] nickDeclarations = JavaPsiFacade.getInstance(myProject).findClasses(TYPE_QUALIFIER_NICKNAME, scope);
for (PsiClass tqNick : nickDeclarations) {
result.addAll(ContainerUtil.findAll(MetaAnnotationUtil.getChildren(tqNick, scope), NullableNotNullManagerImpl::isNullabilityNickName));
}
if (nickDeclarations.length == 0) {
result.addAll(getUnresolvedNicknameUsages());
}
return CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT);
});
}
// some frameworks use jsr305 annotations but don't have them in classpath
private List<PsiClass> getUnresolvedNicknameUsages() {
List<PsiClass> result = new ArrayList<>();
Collection<PsiAnnotation> annotations = JavaAnnotationIndex.getInstance().get(StringUtil.getShortName(TYPE_QUALIFIER_NICKNAME), myProject, GlobalSearchScope.allScope(myProject));
for (PsiAnnotation annotation : annotations) {
PsiElement context = annotation.getContext();
if (context instanceof PsiModifierList && context.getContext() instanceof PsiClass) {
PsiClass ownerClass = (PsiClass)context.getContext();
if (ownerClass.isAnnotationType() && isNullabilityNickName(ownerClass)) {
result.add(ownerClass);
}
}
}
return result;
}
private static boolean isNullabilityNickName(@NotNull PsiClass candidate) {
String qname = candidate.getQualifiedName();
if (qname == null || qname.startsWith("javax.annotation.")) return false;
return getNickNamedNullability(candidate) != Nullness.UNKNOWN;
}
private static Nullness getNickNamedNullability(@NotNull PsiClass psiClass) {
if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return Nullness.UNKNOWN;
@@ -1,15 +1,11 @@
import javax.annotation.meta.*;
@TypeQualifierNickname()
@javax.annotation.Nonnull(when = When.MAYBE)
@interface NullableNick {}
interface UnknownInterface {
void foo(String s);
}
class ImplWithNotNull implements UnknownInterface {
public void foo(@NullableNick String s) {
public void foo(@bar.NullableNick String s) {
System.out.println(s.<warning descr="Method invocation 'hashCode' may produce 'java.lang.NullPointerException'">hashCode</warning>());
}
}
@@ -369,16 +369,28 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
}
public void testTypeQualifierNickname() {
myFixture.addClass("package javax.annotation.meta; public @interface TypeQualifierNickname {}");
addJavaxNullabilityAnnotations(myFixture);
addNullableNick();
myFixture.addClass("package bar;" +
"import javax.annotation.meta.*;" +
"@TypeQualifierNickname() @javax.annotation.NonNull(when = Maybe.MAYBE) " +
"public @interface NullableNick {}");
doTest();
}
public void testTypeQualifierNicknameWithoutDeclarations() {
addJavaxNullabilityAnnotations(myFixture);
addNullableNick();
myFixture.enableInspections(new DataFlowInspection());
myFixture.testHighlighting(true, false, true, "TypeQualifierNickname.java");
}
private void addNullableNick() {
myFixture.addClass("package bar;" +
"@javax.annotation.meta.TypeQualifierNickname() " +
"@javax.annotation.Nonnull(when = javax.annotation.meta.When.MAYBE) " +
"public @interface NullableNick {}");
}
public static void addJavaxDefaultNullabilityAnnotations(final JavaCodeInsightTestFixture fixture) {
fixture.addClass("package javax.annotation;" +
"@javax.annotation.meta.TypeQualifierDefault(java.lang.annotation.ElementType.PARAMETER) @javax.annotation.Nonnull " +
@@ -393,8 +405,6 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
"public @interface TypeQualifierDefault { java.lang.annotation.ElementType[] value() default {};}");
fixture.addClass("package javax.annotation.meta;" +
"public enum When { ALWAYS, UNKNOWN, MAYBE, NEVER }");
fixture.addClass("package javax.annotation.meta;" +
"public @interface TypeQualifierNickname {}");
fixture.addClass("package javax.annotation;" +
"import javax.annotation.meta.*;" +