Java: Handle generic parameters in the inspection "Non-accessible type is exposed" (IDEA-162768)

This commit is contained in:
Pavel Dolgov
2016-11-11 15:22:36 +03:00
parent 020d2e48ad
commit fbb404203f
2 changed files with 140 additions and 24 deletions
@@ -71,7 +71,9 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
private final ModuleFileIndex myModuleFileIndex;
private final Set<String> myExportedPackageNames;
public NonAccessibleTypeExposedVisitor(ProblemsHolder holder, Module module, Set<String> exportedPackageNames) {
public NonAccessibleTypeExposedVisitor(@NotNull ProblemsHolder holder,
@NotNull Module module,
@NotNull Set<String> exportedPackageNames) {
myHolder = holder;
myModuleFileIndex = ModuleRootManager.getInstance(module).getFileIndex();
myExportedPackageNames = exportedPackageNames;
@@ -84,6 +86,7 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
if (!method.isConstructor()) {
checkType(method.getReturnType(), method.getReturnTypeElement());
}
checkTypeParameters(method.getTypeParameterList());
for (PsiParameter parameter : method.getParameterList().getParameters()) {
checkType(parameter.getType(), parameter.getTypeElement());
}
@@ -128,22 +131,68 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
}
}
private void checkType(@Nullable PsiType type, @Nullable PsiTypeElement typeElement) {
if (typeElement != null) {
PsiClass psiClass = PsiUtil.resolveClassInType(type);
checkType(psiClass, typeElement);
@Override
public void visitClass(PsiClass aClass) {
super.visitClass(aClass);
if (isModulePublicApi(aClass)) {
checkTypeParameters(aClass.getTypeParameterList());
}
}
private void checkType(PsiClass psiClass, @NotNull PsiElement typeElement) {
private void checkType(@Nullable PsiType type, @Nullable PsiTypeElement typeElement) {
if (typeElement != null) {
if (type instanceof PsiWildcardType) {
type = ((PsiWildcardType)type).getBound();
PsiElement lastChild = typeElement.getLastChild();
if (lastChild instanceof PsiTypeElement) {
typeElement = (PsiTypeElement)lastChild;
}
}
PsiClass psiClass = PsiUtil.resolveClassInType(type);
checkType(psiClass, typeElement);
if (type instanceof PsiClassType && !(psiClass instanceof PsiTypeParameter)) {
PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement();
if (referenceElement != null) {
checkTypeParameters(referenceElement.getParameterList());
}
}
}
}
private void checkType(@Nullable PsiClass psiClass, @NotNull PsiElement typeElement) {
if (psiClass != null && !(psiClass instanceof PsiTypeParameter) && isInModuleSource(psiClass) && !isModulePublicApi(psiClass)) {
myHolder.registerProblem(typeElement, CLASS_IS_NOT_EXPORTED);
}
}
private void checkTypeParameters(@Nullable PsiReferenceParameterList parameterList) {
if (parameterList != null) {
PsiTypeElement[] typeParameterElements = parameterList.getTypeParameterElements();
for (PsiTypeElement typeParameterElement : typeParameterElements) {
checkType(typeParameterElement.getType(), typeParameterElement);
}
}
}
private void checkTypeParameters(@Nullable PsiTypeParameterList parameterList) {
if (parameterList != null) {
for (PsiTypeParameter typeParameter : parameterList.getTypeParameters()) {
for (PsiJavaCodeReferenceElement referenceElement : typeParameter.getExtendsList().getReferenceElements()) {
PsiElement resolved = referenceElement.resolve();
if (resolved instanceof PsiClass) {
checkType((PsiClass)resolved, referenceElement);
}
checkTypeParameters(referenceElement.getParameterList());
}
}
}
}
@Contract("null -> false")
private boolean isModulePublicApi(@Nullable PsiMember member) {
if (member != null && (member.hasModifierProperty(PsiModifier.PUBLIC) || member.hasModifierProperty(PsiModifier.PROTECTED))) {
if (member != null &&
!(member instanceof PsiTypeParameter) &&
(member.hasModifierProperty(PsiModifier.PUBLIC) || member.hasModifierProperty(PsiModifier.PROTECTED))) {
PsiElement parent = member.getParent();
if (parent instanceof PsiClass) {
return isModulePublicApi((PsiClass)parent);
@@ -160,27 +209,26 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec
private boolean isModulePublicApi(@Nullable PsiAnnotationOwner owner) {
if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList)owner).getParent();
if (parent instanceof PsiMember) {
if (parent instanceof PsiMember) { // class or field or method
return isModulePublicApi((PsiMember)parent);
}
if (parent instanceof PsiParameter) {
if (parent instanceof PsiParameter) { // method parameter
PsiElement declarationScope = ((PsiParameter)parent).getDeclarationScope();
if (declarationScope instanceof PsiMethod) {
return isModulePublicApi((PsiMethod)declarationScope);
}
}
}
else if (owner instanceof PsiTypeElement) {
PsiElement parent = ((PsiTypeElement)owner).getParent();
if (parent instanceof PsiMember) {
return isModulePublicApi((PsiMember)parent);
}
if (parent instanceof PsiReferenceParameterList) {
PsiMethod method = PsiTreeUtil.getParentOfType(parent, PsiMethod.class);
return isModulePublicApi(method);
else if (owner instanceof PsiTypeElement) { // type argument (aka type_use)
PsiElement grandParent = PsiTreeUtil.skipParentsOfType(((PsiTypeElement)owner),
PsiParameter.class, PsiParameterList.class, PsiTypeElement.class,
PsiReferenceList.class, PsiReferenceParameterList.class,
PsiJavaCodeReferenceElement.class);
if (grandParent instanceof PsiMember) {
return isModulePublicApi((PsiMember)grandParent);
}
}
else if (owner instanceof PsiTypeParameter) {
else if (owner instanceof PsiTypeParameter) { // type parameter declaration
PsiElement parent = ((PsiTypeParameter)owner).getParent();
if (parent instanceof PsiTypeParameterList) {
PsiElement grandParent = parent.getParent();
@@ -31,6 +31,7 @@ class Java9NonAccessibleTypeExposedTest : LightJava9ModulesCodeInsightFixtureTes
super.setUp()
myFixture.enableInspections(Java9NonAccessibleTypeExposedInspection())
addFile("module-info.java", "module MAIN { exports apiPkg; exports otherPkg; requires M2; }", MAIN)
addFile("module-info.java", "module M2 { exports m2Pkg; }", M2)
add("apiPkg", "PublicApi", "public class PublicApi {}")
add("apiPkg", "PackageLocal", "class PackageLocal {}")
add("otherPkg", "PublicOther", "public class PublicOther {}")
@@ -40,13 +41,18 @@ class Java9NonAccessibleTypeExposedTest : LightJava9ModulesCodeInsightFixtureTes
highlight("""package apiPkg;
public class Highlighted {
public int i;
protected int getInt() {return 1;}
public int getInt() {return i;}
public void setInt(int n) {i=n;}
protected Long l;
protected Long getLong() {return l;}
protected void setLong(Long n) {l=n;}
public void run() {}
}""")
}
fun testImported() {
addFile("module-info.java", "module M2 { exports m2Pkg; }", M2)
fun testExported() {
addFile("m2Pkg/Exported.java", "package m2Pkg; public class Exported {}", M2)
highlight("""package apiPkg;
@@ -271,17 +277,79 @@ public class Highlighted {
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface PackageLocalAnnotation {}
public class C1<@PublicAnnotation T> {
public List<@PublicAnnotation String> text;
public void foo(Set<@PublicAnnotation String> s) {}
protected <@PublicAnnotation X> void bar(X x) {}
protected List<@PublicAnnotation T> baz() { return new ArrayList<@PublicAnnotation T>();}
protected Set<@PublicAnnotation T> baz() {return new HashSet<@PublicAnnotation T>();}
public List<@PublicAnnotation String> text() {return new ArrayList<@PublicAnnotation String>();}
}
public class C2<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> T> {
public List<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> String> text;
public void foo(Set<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> String> s) {}
protected <@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> X> void bar(X x) {}
protected List<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> T> baz() {
return new ArrayList<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> T>();
protected Set<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> T> baz() {
return new HashSet<@PackageLocalAnnotation T>();
}
public List<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> String> text() {
return new ArrayList<@PackageLocalAnnotation String>();
}
}
public interface I1 extends List<@PublicAnnotation Highlighted> {}
public interface I2 extends List<@<warning descr="The class is not exported from the module">PackageLocalAnnotation</warning> Highlighted> {}
}
""")
}
fun testGenericPublic() {
add("apiPkg", "MyInterface", "public interface MyInterface {}")
add("apiPkg", "MyClass", "public class MyClass implements MyInterface {}")
highlight("""package apiPkg;
import java.util.*;
public class Highlighted<T extends MyInterface> {
protected Set<T> get1() { return new HashSet<>();}
public Set<MyClass> get2() { return new HashSet<MyClass>();}
protected Set<? extends MyClass> get3() { return new HashSet<>();}
public <X extends Object&MyInterface> Set<X> get4() { return new HashSet<>();}
public Map<String, Set<MyInterface>> get5() {return new HashMap<>();}
public void copy1(Set<MyInterface> s) {}
public void copy2(Set<? super MyClass> s) {}
public static class Nested1<T extends MyClass&Iterable<MyInterface>> {
public Iterator<MyInterface> iterator() {return null;}
}
public static class Nested2<T extends MyInterface&AutoCloseable> {
public void close(){}
}
public interface Nested3<X extends Iterable<MyInterface>> {}
public interface Nested4<X extends Iterable<? extends MyInterface>> {}
}
""")
}
fun testGenericNotExported() {
add("implPkg", "MyInterface", "public interface MyInterface {}")
add("implPkg", "MyClass", "public class MyClass implements MyInterface {}")
highlight("""package apiPkg;
import java.util.*;
import implPkg.*;
public class Highlighted<T extends <warning descr="The class is not exported from the module">MyInterface</warning>> {
protected Set<T> get1() { return new HashSet<>();}
public Set<<warning descr="The class is not exported from the module">MyClass</warning>> get2() { return new HashSet<MyClass>();}
protected Set<? extends <warning descr="The class is not exported from the module">MyClass</warning>> get3() { return new HashSet<>();}
public <X extends Object&<warning descr="The class is not exported from the module">MyInterface</warning>> Set<X> get4() { return new HashSet<>();}
public Map<String, Set<<warning descr="The class is not exported from the module">MyInterface</warning>>> get5() {return new HashMap<>();}
public void copy1(Set<<warning descr="The class is not exported from the module">MyInterface</warning>> s) {}
public void copy2(Set<? super <warning descr="The class is not exported from the module">MyClass</warning>> s) {}
public static class Nested1<T extends <warning descr="The class is not exported from the module">MyClass</warning>&
Iterable<<warning descr="The class is not exported from the module">MyInterface</warning>>> {
public Iterator<<warning descr="The class is not exported from the module">MyInterface</warning>> iterator() {return null;}
}
public static class Nested2<T extends <warning descr="The class is not exported from the module">MyInterface</warning>&AutoCloseable> {
public void close() {}
}
public interface Nested3<X extends Iterable<<warning descr="The class is not exported from the module">MyInterface</warning>>> {}
public interface Nested4<X extends Iterable<? extends <warning descr="The class is not exported from the module">MyInterface</warning>>> {}
}
""")
}