Java: Handle most common cases of annotations in the inspection "Non-accessible type is exposed" (IDEA-162768)

This commit is contained in:
Pavel Dolgov
2016-11-10 16:50:58 +03:00
parent cadf629b2b
commit bbcbea29b1
2 changed files with 97 additions and 13 deletions
@@ -65,6 +65,7 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
}
private static class NonAccessibleTypeExposedVisitor extends JavaElementVisitor {
public static final String CLASS_IS_NOT_EXPORTED = "The class is not exported from the module";
private final ProblemsHolder myHolder;
private final ModuleFileIndex myModuleFileIndex;
private final Set<String> myExportedPackageNames;
@@ -96,18 +97,39 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
}
}
@Override
public void visitAnnotation(PsiAnnotation annotation) {
super.visitAnnotation(annotation);
PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
if (referenceElement != null) {
PsiElement resolved = referenceElement.resolve();
if (resolved instanceof PsiClass && !isModulePublicApi((PsiClass)resolved)) {
PsiAnnotationOwner owner = annotation.getOwner();
if (isModulePublicApi(owner)) {
myHolder.registerProblem(referenceElement, CLASS_IS_NOT_EXPORTED);
}
if (owner instanceof PsiParameter) {
PsiElement parent = ((PsiParameter)owner).getParent();
if (parent instanceof PsiMember && isModulePublicApi((PsiMember)parent)) {
myHolder.registerProblem(referenceElement, CLASS_IS_NOT_EXPORTED);
}
}
}
}
}
private void checkType(@Nullable PsiType type, @Nullable PsiTypeElement typeElement) {
if (typeElement != null) {
PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (psiClass != null && isInModuleSource(psiClass) && !isModulePublicApi(psiClass)) {
myHolder.registerProblem(typeElement, "The class is not exported from the module");
myHolder.registerProblem(typeElement, CLASS_IS_NOT_EXPORTED);
}
}
}
@Contract("null -> false")
private boolean isModulePublicApi(@Nullable PsiMember member) {
if (member != null && isModulePublicApiMember(member)) {
if (member != null && (member.hasModifierProperty(PsiModifier.PUBLIC) || member.hasModifierProperty(PsiModifier.PROTECTED))) {
PsiElement parent = member.getParent();
if (parent instanceof PsiClass) {
return isModulePublicApi((PsiClass)parent);
@@ -120,12 +142,27 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
return false;
}
private static boolean isModulePublicApiMember(@NotNull PsiMember member) {
if (member.hasModifierProperty(PsiModifier.PUBLIC) || member.hasModifierProperty(PsiModifier.PROTECTED)) {
return true;
@Contract("null -> false")
private boolean isModulePublicApi(@Nullable PsiAnnotationOwner owner) {
if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList)owner).getParent();
if (parent instanceof PsiMember) {
return isModulePublicApi((PsiMember)parent);
}
if (parent instanceof PsiParameter) {
PsiElement declarationScope = ((PsiParameter)parent).getDeclarationScope();
if (declarationScope instanceof PsiMethod) {
return isModulePublicApi((PsiMethod)declarationScope);
}
}
}
PsiClass containingClass = member.getContainingClass();
return containingClass != null && containingClass.isInterface();
else if (owner instanceof PsiTypeElement) {
PsiElement parent = ((PsiTypeElement)owner).getParent();
if (parent instanceof PsiMember) {
return isModulePublicApi((PsiMember)parent);
}
}
return false;
}
private boolean isInModuleSource(@NotNull PsiClass psiClass) {
@@ -31,9 +31,9 @@ class Java9NonAccessibleTypeExposedTest : LightJava9ModulesCodeInsightFixtureTes
super.setUp()
myFixture.enableInspections(Java9NonAccessibleTypeExposedInspection())
addFile("module-info.java", "module MAIN { exports apiPkg; exports otherPkg; requires M2; }", MAIN)
addFile("apiPkg/PublicApi.java", "package apiPkg; public class PublicApi {}", MAIN)
addFile("apiPkg/PackageLocal.java", "package apiPkg; class PackageLocal {}", MAIN)
addFile("otherPkg/PublicOther.java", "package otherPkg; public class PublicOther {}", MAIN)
add("apiPkg", "PublicApi", "public class PublicApi {}")
add("apiPkg", "PackageLocal", "class PackageLocal {}")
add("otherPkg", "PublicOther", "public class PublicOther {}")
}
fun testPrimitives() {
@@ -168,7 +168,7 @@ public interface Highlighted {
}
fun testNotExportedPackage() {
addFile("implPkg/NotExported.java", "package implPkg; public class NotExported {}", MAIN)
add("implPkg", "NotExported", "public class NotExported {}")
highlight("""package apiPkg;
import implPkg.NotExported;
public class Highlighted {
@@ -182,11 +182,11 @@ public class Highlighted {
}
fun testDoubleNested() {
addFile("apiPkg/PublicOuter.java", """package apiPkg; public class PublicOuter {
add("apiPkg", "PublicOuter", """public class PublicOuter {
static class PackageLocal {
public class DoubleNested {}
}
}""", MAIN)
}""")
highlight("""package apiPkg;
import apiPkg.PublicOuter.PackageLocal;
public class Highlighted {
@@ -197,9 +197,56 @@ public class Highlighted {
""")
}
fun testPublicAnnotation() {
add("apiPkg", "MyAnnotation", "public @interface MyAnnotation {}")
highlight("""package apiPkg;
public class Highlighted {
@MyAnnotation public PublicApi field;
@MyAnnotation public Highlighted() {}
public Highlighted(@MyAnnotation PublicApi s) {field=s;}
@MyAnnotation protected void init() {}
protected @MyAnnotation PublicApi peek() {return field;}
public void set(@MyAnnotation PublicApi s) {field=s;}
}
""")
}
fun testPackageLocalAnnotation() {
add("apiPkg", "MyAnnotation", "@interface MyAnnotation {}")
highlight("""package apiPkg;
public class Highlighted {
@<warning descr="The class is not exported from the module">MyAnnotation</warning> public PublicApi field;
@<warning descr="The class is not exported from the module">MyAnnotation</warning> public Highlighted() {}
public Highlighted(@<warning descr="The class is not exported from the module">MyAnnotation</warning> PublicApi s) {field=s;}
@<warning descr="The class is not exported from the module">MyAnnotation</warning> protected void init() {}
protected @<warning descr="The class is not exported from the module">MyAnnotation</warning> PublicApi peek() {return field;}
public void set(@<warning descr="The class is not exported from the module">MyAnnotation</warning> PublicApi s) {field=s;}
}
""")
}
fun testNotExportedAnnotation() {
add("implPkg", "MyAnnotation", "public @interface MyAnnotation {}")
highlight("""package apiPkg;
import implPkg.MyAnnotation;
public class Highlighted {
@<warning descr="The class is not exported from the module">MyAnnotation</warning> public PublicApi field;
@<warning descr="The class is not exported from the module">MyAnnotation</warning> public Highlighted() {}
public Highlighted(@<warning descr="The class is not exported from the module">MyAnnotation</warning> PublicApi s) {field=s;}
@<warning descr="The class is not exported from the module">MyAnnotation</warning> protected void init() {}
protected @<warning descr="The class is not exported from the module">MyAnnotation</warning> PublicApi peek() {return field;}
public void set(@<warning descr="The class is not exported from the module">MyAnnotation</warning> PublicApi s) {field=s;}
}
""")
}
private fun highlight(@Language("JAVA") @NotNull @NonNls text: String) {
val file = addFile("apiPkg/Highlighted.java", text, MAIN)
myFixture.configureFromExistingVirtualFile(file)
myFixture.checkHighlighting()
}
private fun add(packageName: String, className: String, @Language("JAVA") @NotNull @NonNls text: String) {
addFile("$packageName/$className.java", "package $packageName; $text", MAIN)
}
}