Java: Support Jigsaw modules in calculation of class usages and visibility - added inspection setting for exported packages (IDEA-169200, IDEA-169204)

This commit is contained in:
Pavel Dolgov
2017-06-30 13:24:19 +03:00
parent f21f5d8061
commit 67058ae907
15 changed files with 342 additions and 138 deletions
@@ -35,9 +35,15 @@ public interface RefJavaModule extends RefElement {
@NotNull
Map<String, List<String>> getExportedPackageNames();
@NotNull
Set<RefClass> getServiceInterfaces();
@NotNull
Set<RefClass> getServiceImplementations();
@NotNull
Set<RefClass> getUsedServices();
@NotNull
List<RequiredModule> getRequiredModules();
@@ -16,8 +16,8 @@
package com.intellij.codeInspection.java19modules;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
import com.intellij.codeInspection.reference.EntryPoint;
import com.intellij.codeInspection.reference.RefElement;
import com.intellij.codeInspection.reference.*;
import com.intellij.codeInspection.visibility.EntryPointWithVisibilityLevel;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.pom.java.LanguageLevel;
@@ -30,16 +30,20 @@ import com.intellij.util.xmlb.XmlSerializer;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jdom.Element;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Pavel.Dolgov
*/
public class Java9ModuleEntryPoint extends EntryPoint {
public class Java9ModuleEntryPoint extends EntryPointWithVisibilityLevel {
public static final String ID = "moduleInfo";
public boolean ADD_EXPORTED_PACKAGES_AND_SERVICES_TO_ENTRIES = true;
@NotNull
@@ -56,54 +60,126 @@ public class Java9ModuleEntryPoint extends EntryPoint {
@Override
public boolean isEntryPoint(@NotNull PsiElement psiElement) {
if (psiElement instanceof PsiClass) {
return isExported((PsiClass)psiElement);
return isServiceOrExported((PsiClass)psiElement);
}
if (psiElement instanceof PsiMethod) {
PsiMethod method = (PsiMethod)psiElement;
if (isDefaultConstructor(method) || isProviderMethod(method)) {
return isExported(method.getContainingClass());
return isServiceOrExported(method.getContainingClass());
}
}
return false;
}
private static boolean isDefaultConstructor(PsiMethod method) {
@Override
public int getMinVisibilityLevel(PsiMember member) {
if (member instanceof PsiClass) {
final PsiJavaModule javaModule = getJavaModule(member);
if (javaModule != null &&
!isServiceClass((PsiClass)member, javaModule) &&
isInExportedPackage((PsiClass)member, javaModule)) {
return PsiUtil.ACCESS_LEVEL_PACKAGE_LOCAL;
}
}
return -1;
}
@Override
public String getTitle() {
return "Suggest package-private visibility level for classes in exported packages (Java 9+)";
}
@Override
public String getId() {
return ID;
}
@Override
public boolean keepVisibilityLevel(boolean entryPointEnabled, RefJavaElement refJavaElement) {
if (refJavaElement instanceof RefClass) {
RefClass refClass = (RefClass)refJavaElement;
RefModule refModule = refClass.getModule();
if (refModule != null) {
RefJavaModule refJavaModule = RefJavaModule.JAVA_MODULE.get(refModule);
if (refJavaModule != null) {
return isServiceClass(refClass, refJavaModule) ||
!entryPointEnabled && isInExportedPackage(refClass, refJavaModule);
}
}
}
return false;
}
private static boolean isInExportedPackage(@Nullable RefClass refClass, @NotNull RefJavaModule refJavaModule) {
RefEntity refOwner = refClass;
while (refOwner instanceof RefClass) {
String modifier = ((RefClass)refOwner).getAccessModifier();
refOwner = PsiModifier.PUBLIC.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) ? refOwner.getOwner() : null;
}
if (refOwner instanceof RefPackage) {
Map<String, List<String>> exportedPackageNames = refJavaModule.getExportedPackageNames();
if (exportedPackageNames.containsKey(refOwner.getQualifiedName())) {
return true;
}
}
return false;
}
private static boolean isServiceClass(@Nullable RefClass refClass, @NotNull RefJavaModule refJavaModule) {
return refJavaModule.getServiceInterfaces().contains(refClass) ||
refJavaModule.getServiceImplementations().contains(refClass) ||
refJavaModule.getUsedServices().contains(refClass);
}
private static boolean isDefaultConstructor(@NotNull PsiMethod method) {
return method.isConstructor() &&
method.getParameterList().getParametersCount() == 0 &&
method.hasModifierProperty(PsiModifier.PUBLIC);
}
private static boolean isProviderMethod(PsiMethod method) {
private static boolean isProviderMethod(@NotNull PsiMethod method) {
return "provider".equals(method.getName()) &&
method.getParameterList().getParametersCount() == 0 &&
method.hasModifierProperty(PsiModifier.PUBLIC) &&
method.hasModifierProperty(PsiModifier.STATIC);
}
private static boolean isExported(@Nullable PsiClass psiClass) {
if (psiClass != null) {
String className = psiClass.getQualifiedName();
if (className != null) {
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(psiClass);
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) {
PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(psiClass);
if (javaModule != null) {
String packageName = getPublicApiPackageName(psiClass);
if (packageName != null) {
Set<String> exportedPackageNames = getExportedPackageNames(javaModule);
if (exportedPackageNames.contains(packageName)) {
return true;
}
}
Set<String> serviceImplementationNames = getServiceImplementationNames(javaModule);
return serviceImplementationNames.contains(className);
}
}
private static boolean isServiceOrExported(@Nullable PsiClass psiClass) {
PsiJavaModule javaModule = getJavaModule(psiClass);
return javaModule != null && (isServiceClass(psiClass, javaModule) || isInExportedPackage(psiClass, javaModule));
}
private static boolean isInExportedPackage(@NotNull PsiClass psiClass, @NotNull PsiJavaModule javaModule) {
String packageName = getPublicApiPackageName(psiClass);
if (packageName != null) {
Set<String> exportedPackageNames = getExportedPackageNames(javaModule);
if (exportedPackageNames.contains(packageName)) {
return true;
}
}
return false;
}
private static boolean isServiceClass(@NotNull PsiClass psiClass, @NotNull PsiJavaModule javaModule) {
Set<String> serviceClassNames = CachedValuesManager.getCachedValue(
javaModule, () -> CachedValueProvider.Result.create(collectServiceClassNames(javaModule), javaModule));
return serviceClassNames.contains(psiClass.getQualifiedName());
}
@Contract("null -> null")
@Nullable
private static PsiJavaModule getJavaModule(@Nullable PsiElement element) {
if (element != null) {
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(element);
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) {
return JavaModuleGraphUtil.findDescriptorByElement(element);
}
}
return null;
}
private static String getPublicApiPackageName(PsiClass psiClass) {
if (psiClass != null && (psiClass.hasModifierProperty(PsiModifier.PUBLIC) || psiClass.hasModifierProperty(PsiModifier.PROTECTED))) {
PsiElement parent = psiClass.getParent();
@@ -128,18 +204,25 @@ public class Java9ModuleEntryPoint extends EntryPoint {
}
@NotNull
private static Set<String> getServiceImplementationNames(@NotNull PsiJavaModule javaModule) {
return CachedValuesManager.getCachedValue(javaModule, () -> {
Set<String> classes = StreamEx.of(javaModule.getProvides().iterator())
.map(PsiProvidesStatement::getImplementationList)
.nonNull()
.map(PsiReferenceList::getReferenceElements)
.flatMap(Arrays::stream)
.map(PsiJavaCodeReferenceElement::getQualifiedName)
.nonNull()
.toCollection(THashSet::new);
return CachedValueProvider.Result.create(classes, javaModule);
});
private static Set<String> collectServiceClassNames(@NotNull PsiJavaModule javaModule) {
Set<String> classes = StreamEx.of(javaModule.getProvides().spliterator())
.map(PsiProvidesStatement::getImplementationList)
.nonNull()
.map(PsiReferenceList::getReferenceElements)
.flatMap(Arrays::stream)
.map(PsiJavaCodeReferenceElement::getQualifiedName)
.nonNull()
.toCollection(THashSet::new);
Set<String> usages = StreamEx.of(javaModule.getUses().iterator())
.map(PsiUsesStatement::getClassReference)
.nonNull()
.map(PsiJavaCodeReferenceElement::getQualifiedName)
.nonNull()
.toCollection(THashSet::new);
classes.addAll(usages);
return classes;
}
@Override
@@ -33,7 +33,9 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule {
private final RefModule myRefModule;
private Map<String, List<String>> myExportedPackageNames;
private Set<RefClass> myServiceInterfaces;
private Set<RefClass> myServiceImplementations;
private Set<RefClass> myUsedServices;
private List<RequiredModule> myRequiredModules;
RefJavaModuleImpl(@NotNull PsiJavaModule javaModule, @NotNull RefManagerImpl manager) {
@@ -75,104 +77,152 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule {
return myExportedPackageNames != null ? myExportedPackageNames : Collections.emptyMap();
}
@NotNull
@Override
public Set<RefClass> getServiceInterfaces() {
return myServiceInterfaces != null ? myServiceInterfaces : Collections.emptySet();
}
@NotNull
@Override
public Set<RefClass> getServiceImplementations() {
return myServiceImplementations != null ? myServiceImplementations : Collections.emptySet();
}
@NotNull
@Override
public Set<RefClass> getUsedServices() {
return myUsedServices != null ? myUsedServices : Collections.emptySet();
}
@Override
@NotNull
public List<RequiredModule> getRequiredModules() {
return myRequiredModules != null ? myRequiredModules : Collections.emptyList();
}
@Override
public void buildReferences() {
PsiJavaModule javaModule = getElement();
if (javaModule != null) {
for (PsiRequiresStatement statement : javaModule.getRequires()) {
PsiJavaModuleReferenceElement referenceElement = statement.getReferenceElement();
private void buildRequiresReferences(PsiJavaModule javaModule) {
for (PsiRequiresStatement statement : javaModule.getRequires()) {
PsiJavaModuleReferenceElement referenceElement = statement.getReferenceElement();
if (referenceElement != null) {
PsiElement element = addReference(referenceElement.getReference());
if (element instanceof PsiJavaModule) {
PsiJavaModule requiredModule = (PsiJavaModule)element;
Map<String, List<String>> packagesExportedByModule = getPackagesExportedByModule(requiredModule);
if (myRequiredModules == null) myRequiredModules = new ArrayList<>(1);
myRequiredModules.add(new RequiredModule(requiredModule.getName(), packagesExportedByModule, statement.hasModifierProperty(PsiModifier.TRANSITIVE)));
}
}
}
}
private void buildExportsReferences(PsiJavaModule javaModule) {
List<String> emptyList = Collections.emptyList();
for (PsiPackageAccessibilityStatement statement : javaModule.getExports()) {
PsiElement element = addReference(statement.getPackageReference());
String packageName = null;
if (element instanceof PsiPackage) {
packageName = ((PsiPackage)element).getQualifiedName();
if (myExportedPackageNames == null) myExportedPackageNames = new THashMap<>(1);
myExportedPackageNames.put(packageName, emptyList);
}
for (PsiJavaModuleReferenceElement referenceElement : statement.getModuleReferences()) {
if (referenceElement != null) {
PsiElement element = addReference(referenceElement.getReference());
if (element instanceof PsiJavaModule) {
PsiJavaModule requiredModule = (PsiJavaModule)element;
Map<String, List<String>> packagesExportedByModule = getPackagesExportedByModule(requiredModule);
if (myRequiredModules == null) myRequiredModules = new ArrayList<>(1);
myRequiredModules.add(new RequiredModule(requiredModule.getName(), packagesExportedByModule, statement.hasModifierProperty(PsiModifier.TRANSITIVE)));
PsiElement moduleElement = addReference(referenceElement.getReference());
if (packageName != null && moduleElement instanceof PsiJavaModule) {
List<String> toModuleNames = myExportedPackageNames.get(packageName);
if (toModuleNames == emptyList) myExportedPackageNames.put(packageName, toModuleNames = new ArrayList<>(1));
toModuleNames.add(((PsiJavaModule)moduleElement).getName());
}
}
}
List<String> emptyList = Collections.emptyList();
for (PsiPackageAccessibilityStatement statement : javaModule.getExports()) {
PsiElement element = addReference(statement.getPackageReference());
String packageName = null;
if (element instanceof PsiPackage) {
packageName = ((PsiPackage)element).getQualifiedName();
if (myExportedPackageNames == null) myExportedPackageNames = new THashMap<>(1);
myExportedPackageNames.put(packageName, emptyList);
}
for (PsiJavaModuleReferenceElement referenceElement : statement.getModuleReferences()) {
if (referenceElement != null) {
PsiElement moduleElement = addReference(referenceElement.getReference());
if (packageName != null && moduleElement instanceof PsiJavaModule) {
List<String> toModuleNames = myExportedPackageNames.get(packageName);
if (toModuleNames == emptyList) myExportedPackageNames.put(packageName, toModuleNames = new ArrayList<>(1));
toModuleNames.add(((PsiJavaModule)moduleElement).getName());
}
}
}
}
for (PsiProvidesStatement statement : javaModule.getProvides()) {
final PsiJavaCodeReferenceElement interfaceReference = statement.getInterfaceReference();
final PsiReferenceList implementationList = statement.getImplementationList();
if (interfaceReference != null && implementationList != null) {
final PsiElement providerInterface = interfaceReference.resolve();
if (providerInterface instanceof PsiClass) {
final RefElement refInterface = getRefManager().getReference(providerInterface);
if (refInterface instanceof RefJavaElementImpl) {
for (PsiJavaCodeReferenceElement implementationReference : implementationList.getReferenceElements()) {
final PsiElement implementationClass = implementationReference.resolve();
if (implementationClass instanceof PsiClass) {
RefElement refTargetElement = null;
PsiElement targetElement = getProviderMethod((PsiClass)implementationClass);
}
}
if (targetElement == null) {
final RefElement refClass = getRefManager().getReference(implementationClass);
if (refClass instanceof RefClassImpl) {
if (myServiceImplementations == null) myServiceImplementations = new THashSet<>();
myServiceImplementations.add((RefClass)refClass);
private void buildProvidesReferences(PsiJavaModule javaModule) {
for (PsiProvidesStatement statement : javaModule.getProvides()) {
final PsiJavaCodeReferenceElement interfaceReference = statement.getInterfaceReference();
final PsiReferenceList implementationList = statement.getImplementationList();
if (interfaceReference != null && implementationList != null) {
final PsiElement providerInterface = interfaceReference.resolve();
if (providerInterface instanceof PsiClass) {
final RefElement refInterface = getRefManager().getReference(providerInterface);
if (refInterface instanceof RefClassImpl) {
if (myServiceInterfaces == null) myServiceInterfaces = new THashSet<>();
myServiceInterfaces.add((RefClass)refInterface);
final RefMethod refConstructor = ((RefClassImpl)refClass).getDefaultConstructor();
if (refConstructor != null) {
final PsiModifierListOwner constructorElement = refConstructor.getElement();
if (constructorElement != null && constructorElement.hasModifierProperty(PsiModifier.PUBLIC)) {
refTargetElement = refConstructor;
targetElement = constructorElement;
}
for (PsiJavaCodeReferenceElement implementationReference : implementationList.getReferenceElements()) {
final PsiElement implementationClass = implementationReference.resolve();
if (implementationClass instanceof PsiClass) {
RefElement refTargetElement = null;
PsiElement targetElement = getProviderMethod((PsiClass)implementationClass);
if (targetElement == null) {
final RefElement refClass = getRefManager().getReference(implementationClass);
if (refClass instanceof RefClassImpl) {
if (myServiceImplementations == null) myServiceImplementations = new THashSet<>();
myServiceImplementations.add((RefClass)refClass);
final RefMethod refConstructor = ((RefClassImpl)refClass).getDefaultConstructor();
if (refConstructor != null) {
final PsiModifierListOwner constructorElement = refConstructor.getElement();
if (constructorElement != null && constructorElement.hasModifierProperty(PsiModifier.PUBLIC)) {
refTargetElement = refConstructor;
targetElement = constructorElement;
}
}
}
if (targetElement == null) {
targetElement = implementationClass;
}
if (refTargetElement == null) {
refTargetElement = getRefManager().getReference(targetElement);
}
if (refTargetElement != null) {
((RefJavaElementImpl)refInterface)
.addReference(refTargetElement, targetElement, providerInterface, false, true, null);
}
}
if (targetElement == null) {
targetElement = implementationClass;
}
if (refTargetElement == null) {
refTargetElement = getRefManager().getReference(targetElement);
}
if (refTargetElement != null) {
((RefClassImpl)refInterface)
.addReference(refTargetElement, targetElement, providerInterface, false, true, null);
}
}
}
}
}
}
}
}
private void buildUsesReferences(PsiJavaModule javaModule) {
for (PsiUsesStatement statement : javaModule.getUses()) {
final PsiJavaCodeReferenceElement reference = statement.getClassReference();
if (reference != null) {
final PsiElement usedInterface = reference.resolve();
if (usedInterface instanceof PsiClass) {
final RefElement refClass = getRefManager().getReference(usedInterface);
if (refClass instanceof RefClass) {
if (myUsedServices == null) myUsedServices = new THashSet<>();
myUsedServices.add((RefClass)refClass);
}
}
}
}
}
@Override
public void buildReferences() {
PsiJavaModule javaModule = getElement();
if (javaModule != null) {
buildRequiresReferences(javaModule);
buildExportsReferences(javaModule);
buildProvidesReferences(javaModule);
buildUsesReferences(javaModule);
getRefManager().fireBuildReferences(this);
}
}
/**
* For building references between modules
*/
private PsiElement addReference(PsiPolyVariantReference reference) {
List<PsiElement> resolvedElements = new ArrayList<>();
if (reference != null) {
@@ -16,7 +16,7 @@
package com.intellij.codeInspection.visibility;
import com.intellij.codeInspection.reference.EntryPoint;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.codeInspection.reference.RefJavaElement;
import com.intellij.psi.PsiMember;
import com.intellij.psi.util.PsiUtil;
@@ -39,4 +39,11 @@ public abstract class EntryPointWithVisibilityLevel extends EntryPoint {
* Id to serialize checkbox state in visibility inspection settings
*/
public abstract String getId();
/**
* Don't suggest decreasing visibility for the element, sometimes even if the entry point is disabled.
*/
public boolean keepVisibilityLevel(boolean entryPointEnabled, @SuppressWarnings("unused") RefJavaElement refJavaElement) {
return false;
}
}
@@ -23,6 +23,7 @@ import com.intellij.codeInsight.daemon.impl.IdentifierUtil;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.EntryPointsManager;
import com.intellij.codeInspection.reference.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointName;
@@ -36,10 +37,12 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.components.panels.VerticalBox;
import com.intellij.usageView.UsageViewTypeLocation;
import com.intellij.util.VisibilityUtil;
import one.util.streamex.StreamEx;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
@@ -101,7 +104,7 @@ public class VisibilityInspection extends GlobalJavaBatchInspectionTool {
if (entryPoint instanceof EntryPointWithVisibilityLevel) {
gc.gridy++;
final JCheckBox checkBox = new JCheckBox(((EntryPointWithVisibilityLevel)entryPoint).getTitle());
checkBox.setSelected(myExtensions.getOrDefault(((EntryPointWithVisibilityLevel)entryPoint).getId(), true));
checkBox.setSelected(isEntryPointEnabled((EntryPointWithVisibilityLevel)entryPoint));
checkBox.addActionListener(e -> myExtensions.put(((EntryPointWithVisibilityLevel)entryPoint).getId(), checkBox.isSelected()));
add(checkBox, gc);
}
@@ -181,11 +184,14 @@ public class VisibilityInspection extends GlobalJavaBatchInspectionTool {
//ignore anonymous classes. They do not have access modifiers.
if (refElement instanceof RefClass) {
RefClass refClass = (RefClass) refElement;
if (refClass.isAnonymous() || refClass.isServlet() || refClass.isApplet() || refClass.isLocalClass() || isExported(refClass)) {
if (refClass.isAnonymous() || refClass.isServlet() || refClass.isApplet() || refClass.isLocalClass()) {
return null;
}
}
if (keepVisibilityLevel(refElement)) {
return null;
}
//ignore unreferenced code. They could be a potential entry points.
if (refElement.getInReferences().isEmpty()) {
@@ -215,30 +221,10 @@ public class VisibilityInspection extends GlobalJavaBatchInspectionTool {
return null;
}
private static boolean isExported(RefClass refClass) {
RefModule refModule = refClass.getModule();
if (refModule != null) {
RefJavaModule refJavaModule = RefJavaModule.JAVA_MODULE.get(refModule);
if (refJavaModule != null) {
Set<RefClass> serviceImplementations = refJavaModule.getServiceImplementations();
if (serviceImplementations.contains(refClass)) {
return true;
}
RefEntity refOwner = refClass;
while (refOwner instanceof RefClass) {
String modifier = ((RefClass)refOwner).getAccessModifier();
refOwner = PsiModifier.PUBLIC.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) ? refOwner.getOwner() : null;
}
if (refOwner instanceof RefPackage) {
Map<String, List<String>> exportedPackageNames = refJavaModule.getExportedPackageNames();
if (exportedPackageNames.containsKey(refOwner.getQualifiedName())) {
return true;
}
}
}
}
return false;
private boolean keepVisibilityLevel(RefJavaElement refElement) {
return StreamEx.of(ExtensionPointName.<EntryPoint>create(ToolExtensionPoints.DEAD_CODE_TOOL).getExtensions())
.select(EntryPointWithVisibilityLevel.class)
.anyMatch(point -> point.keepVisibilityLevel(isEntryPointEnabled(point), refElement));
}
@NotNull
@@ -273,13 +259,17 @@ public class VisibilityInspection extends GlobalJavaBatchInspectionTool {
}
int getMinVisibilityLevel(PsiMember member) {
return Arrays.stream(ExtensionPointName.<EntryPoint>create(ToolExtensionPoints.DEAD_CODE_TOOL).getExtensions())
.filter(point -> point instanceof EntryPointWithVisibilityLevel &&
myExtensions.getOrDefault(((EntryPointWithVisibilityLevel)point).getId(), true))
.mapToInt(point -> ((EntryPointWithVisibilityLevel)point).getMinVisibilityLevel(member))
return StreamEx.of(ExtensionPointName.<EntryPoint>create(ToolExtensionPoints.DEAD_CODE_TOOL).getExtensions())
.select(EntryPointWithVisibilityLevel.class)
.filter(point -> isEntryPointEnabled(point))
.mapToInt(point -> point.getMinVisibilityLevel(member))
.max().orElse(-1);
}
private boolean isEntryPointEnabled(EntryPointWithVisibilityLevel point) {
return myExtensions.getOrDefault(point.getId(), true);
}
private int getMinVisibilityLevel(RefJavaElement refElement) {
PsiElement element = refElement.getElement();
if (element instanceof PsiMember) {
@@ -627,6 +617,12 @@ public class VisibilityInspection extends GlobalJavaBatchInspectionTool {
}
}
@TestOnly
public void setEntryPointEnabled(@NotNull String entryPointId, boolean enabled) {
LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode());
myExtensions.put(entryPointId, enabled);
}
private static class AcceptSuggestedAccess implements LocalQuickFix{
private final RefManager myManager;
@PsiModifier.ModifierConstant private final String myHint;
@@ -0,0 +1,3 @@
package foo.bar;
public class Public {}
@@ -0,0 +1,3 @@
package foo.bar;
public class ServiceApi {}
@@ -0,0 +1,3 @@
package foo.bar;
public class ServiceImpl extends ServiceApi {}
@@ -0,0 +1,3 @@
package foo.bar;
public class UsedService {}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
<problem>
<file>Public.java</file>
<line>3</line>
<entry_point TYPE="class" FQNAME="foo.bar.Public" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Declaration access can be weaker</problem_class>
<description>Can be package-private</description>
</problem>
</problems>
@@ -0,0 +1,3 @@
package foo.bar;
public class Api {}
@@ -0,0 +1,4 @@
package foo.bar.impl;
import foo.bar Api;
public class Impl extends Api {}
@@ -0,0 +1,4 @@
package foo.bar.impl;
import foo.bar Api;
public class Other extends Api {}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
<problem>
<file>Other.java</file>
<line>4</line>
<entry_point TYPE="class" FQNAME="foo.bar.impl.Other" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Declaration access can be weaker</problem_class>
<description>Can be package-private</description>
</problem>
</problems>
@@ -16,6 +16,7 @@
package com.intellij.java.codeInspection
import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper
import com.intellij.codeInspection.java19modules.Java9ModuleEntryPoint
import com.intellij.codeInspection.visibility.VisibilityInspection
import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
import com.intellij.openapi.application.ex.PathManagerEx
@@ -33,6 +34,21 @@ class Java9VisibilityTest : LightJava9ModulesCodeInsightFixtureTestCase() {
fun testInheritedService() = doTestService()
fun testProvidedService() = doTestService()
fun testUsedService() {
moduleInfo("module foo.bar { exports foo.bar; uses foo.bar.Api; uses foo.bar.impl.Impl; }")
doTest("foo.bar.Api", "foo.bar.impl.Impl", "foo.bar.impl.Other")
}
fun testReduceVisibilityInExportedPackages() {
moduleInfo("""module foo.bar {
exports foo.bar;
provides foo.bar.ServiceApi with foo.bar.ServiceImpl;
uses foo.bar.UsedService;
}""")
doTest("foo.bar.Public", "foo.bar.ServiceApi", "foo.bar.ServiceImpl", "foo.bar.UsedService",
reduceVisibilityInExportedPackages = true)
}
private fun doTestClass() {
moduleInfo("module foo.bar { exports foo.bar; }")
doTest("foo.bar.Api", "foo.bar.impl.Impl")
@@ -43,11 +59,14 @@ class Java9VisibilityTest : LightJava9ModulesCodeInsightFixtureTestCase() {
doTest("foo.bar.Api", "foo.bar.impl.Impl", "foo.bar.impl.Other")
}
private fun doTest(vararg classNames: String) {
private fun doTest(vararg classNames: String, reduceVisibilityInExportedPackages: Boolean = false) {
val testPath = testDataPath + getTestName(true)
addJavaFiles(testPath, classNames)
val toolWrapper = GlobalInspectionToolWrapper(VisibilityInspection())
val inspection = VisibilityInspection()
inspection.setEntryPointEnabled(Java9ModuleEntryPoint.ID, reduceVisibilityInExportedPackages)
val toolWrapper = GlobalInspectionToolWrapper(inspection)
doGlobalInspectionTest(testPath, toolWrapper)
}
}