diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java index ab4cdfb2ed10..773d54d39a2d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/Java9NonAccessibleTypeExposedInspection.java @@ -71,7 +71,9 @@ public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspec private final ModuleFileIndex myModuleFileIndex; private final Set myExportedPackageNames; - public NonAccessibleTypeExposedVisitor(ProblemsHolder holder, Module module, Set exportedPackageNames) { + public NonAccessibleTypeExposedVisitor(@NotNull ProblemsHolder holder, + @NotNull Module module, + @NotNull Set 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(); diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/Java9NonAccessibleTypeExposedTest.kt b/java/java-tests/testSrc/com/intellij/codeInspection/Java9NonAccessibleTypeExposedTest.kt index 21d34e9e27de..0b93c6b7e656 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/Java9NonAccessibleTypeExposedTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInspection/Java9NonAccessibleTypeExposedTest.kt @@ -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<@PackageLocalAnnotation T> { + public List<@PackageLocalAnnotation String> text; public void foo(Set<@PackageLocalAnnotation String> s) {} protected <@PackageLocalAnnotation X> void bar(X x) {} - protected List<@PackageLocalAnnotation T> baz() { - return new ArrayList<@PackageLocalAnnotation T>(); + protected Set<@PackageLocalAnnotation T> baz() { + return new HashSet<@PackageLocalAnnotation T>(); + } + public List<@PackageLocalAnnotation String> text() { + return new ArrayList<@PackageLocalAnnotation String>(); } } + public interface I1 extends List<@PublicAnnotation Highlighted> {} + public interface I2 extends List<@PackageLocalAnnotation 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 { + protected Set get1() { return new HashSet<>();} + public Set get2() { return new HashSet();} + protected Set get3() { return new HashSet<>();} + public Set get4() { return new HashSet<>();} + public Map> get5() {return new HashMap<>();} + public void copy1(Set s) {} + public void copy2(Set s) {} + + public static class Nested1> { + public Iterator iterator() {return null;} + } + public static class Nested2 { + public void close(){} + } + public interface Nested3> {} + public interface Nested4> {} +} +""") + } + + 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 HighlightedMyInterface> { + protected Set get1() { return new HashSet<>();} + public Set<MyClass> get2() { return new HashSet();} + protected SetMyClass> get3() { return new HashSet<>();} + public MyInterface> Set get4() { return new HashSet<>();} + public MapMyInterface>> get5() {return new HashMap<>();} + public void copy1(Set<MyInterface> s) {} + public void copy2(SetMyClass> s) {} + + public static class Nested1MyClass& + Iterable<MyInterface>> { + public Iterator<MyInterface> iterator() {return null;} + } + public static class Nested2MyInterface&AutoCloseable> { + public void close() {} + } + public interface Nested3MyInterface>> {} + public interface Nested4MyInterface>> {} } """) }