mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Java: Don't traverse the project twice in the global inspection "Redundant 'requires' statement in module-info" (IDEA-163139)
This commit is contained in:
+10
-3
@@ -33,8 +33,15 @@ public interface RefJavaModule extends RefElement {
|
||||
Map<String, List<String>> getExportedPackageNames();
|
||||
|
||||
@NotNull
|
||||
Map<String, Boolean> getRequiredModuleNames();
|
||||
Map<String, Dependency> getRequiredModules();
|
||||
|
||||
@NotNull
|
||||
Map<String, PsiRequiresStatement> getRequiresStatements();
|
||||
class Dependency {
|
||||
@NotNull public final Map<String, List<String>> packageNames;
|
||||
public final boolean isPublic;
|
||||
|
||||
public Dependency(@NotNull Map<String, List<String>> packageNames, boolean isPublic) {
|
||||
this.packageNames = packageNames;
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+78
-134
@@ -29,7 +29,6 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PsiJavaModuleReference;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import gnu.trove.THashMap;
|
||||
import gnu.trove.THashSet;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -44,15 +43,8 @@ import java.util.*;
|
||||
public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance(Java9RedundantRequiresStatementInspection.class);
|
||||
|
||||
// applicable to RefFile and RefModule
|
||||
private static final Key<Set<String>> IMPORTED_JAVA_PACKAGES = Key.create("imported_java_packages");
|
||||
|
||||
// applicable to RefProject
|
||||
// mappings: module name -> exported package name -> export to modules
|
||||
private static final Key<Map<String, Map<String, List<String>>>> EXPORTED_MODULE_PACKAGES =
|
||||
Key.create("exported_module_packages");
|
||||
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -60,29 +52,43 @@ public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchIn
|
||||
return InspectionsBundle.message("inspection.redundant.requires.statement.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runInspection(@NotNull AnalysisScope scope,
|
||||
@NotNull InspectionManager manager,
|
||||
@NotNull GlobalInspectionContext globalContext,
|
||||
@NotNull ProblemDescriptionsProcessor problemDescriptionsProcessor) {
|
||||
super.runInspection(scope, manager, globalContext, problemDescriptionsProcessor);
|
||||
globalContext.getRefManager().iterate(new RedundantRequiresStatementVisitor(manager, problemDescriptionsProcessor));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CommonProblemDescriptor[] checkElement(@NotNull RefEntity refEntity,
|
||||
@NotNull AnalysisScope scope,
|
||||
@NotNull InspectionManager manager,
|
||||
@NotNull GlobalInspectionContext globalContext) {
|
||||
if (refEntity instanceof RefFile) {
|
||||
RefModule refModule = ((RefFile)refEntity).getModule();
|
||||
if (refModule != null) {
|
||||
Set<String> importedPackages = refEntity.getUserData(IMPORTED_JAVA_PACKAGES);
|
||||
if (!ContainerUtil.isEmpty(importedPackages)) {
|
||||
Set<String> moduleImportedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
||||
if (moduleImportedPackages != null) {
|
||||
moduleImportedPackages.addAll(importedPackages);
|
||||
if (refEntity instanceof RefJavaModule) {
|
||||
RefJavaModule refJavaModule = (RefJavaModule)refEntity;
|
||||
|
||||
RefModule refModule = refJavaModule.getModule();
|
||||
PsiJavaModule psiJavaModule = refJavaModule.getElement();
|
||||
if (refModule != null && psiJavaModule != null) {
|
||||
Set<String> moduleImportedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
||||
if (moduleImportedPackages != null) {
|
||||
Map<String, RefJavaModule.Dependency> requiredModules = refJavaModule.getRequiredModules();
|
||||
if (!requiredModules.isEmpty()) {
|
||||
List<CommonProblemDescriptor> descriptors = new ArrayList<>();
|
||||
for (Map.Entry<String, RefJavaModule.Dependency> entry : requiredModules.entrySet()) {
|
||||
String requiredModuleName = entry.getKey();
|
||||
RefJavaModule.Dependency dependency = entry.getValue();
|
||||
|
||||
if (isDependencyUnused(dependency.packageNames, moduleImportedPackages, refJavaModule.getName())) {
|
||||
PsiRequiresStatement requiresStatement = ContainerUtil.find(
|
||||
psiJavaModule.getRequires(), statement -> requiredModuleName.equals(statement.getModuleName()));
|
||||
if (requiresStatement != null) {
|
||||
CommonProblemDescriptor descriptor = manager.createProblemDescriptor(
|
||||
requiresStatement,
|
||||
InspectionsBundle.message("inspection.redundant.requires.statement.description", requiredModuleName),
|
||||
new DeleteRedundantRequiresStatementFix(requiredModuleName, moduleImportedPackages),
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, false);
|
||||
descriptors.add(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ContainerUtil.isEmpty(descriptors)) {
|
||||
return descriptors.toArray(CommonProblemDescriptor.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,25 +118,16 @@ public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchIn
|
||||
return new RedundantRequiresStatementAnnotator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<String, Map<String, List<String>>> getExportedModulePackages(RefManager refManager) {
|
||||
RefProject refProject = refManager.getRefProject();
|
||||
Map<String, Map<String, List<String>>> exportedModulePackages = refProject.getUserData(EXPORTED_MODULE_PACKAGES);
|
||||
if (exportedModulePackages == null) {
|
||||
exportedModulePackages = new THashMap<>();
|
||||
refProject.putUserData(EXPORTED_MODULE_PACKAGES, exportedModulePackages);
|
||||
}
|
||||
return exportedModulePackages;
|
||||
}
|
||||
|
||||
private static PsiJavaModule resolveRequiredModule(PsiRequiresStatement requiresStatement) {
|
||||
return PsiJavaModuleReference.resolve(requiresStatement, requiresStatement.getModuleName(), false);
|
||||
}
|
||||
|
||||
private static class DeleteRedundantRequiresStatementFix implements LocalQuickFix {
|
||||
private String myRequiredModuleName;
|
||||
private Set<String> myImportedPackages;
|
||||
|
||||
public DeleteRedundantRequiresStatementFix(Set<String> importedPackages) {
|
||||
public DeleteRedundantRequiresStatementFix(String requiredModuleName, Set<String> importedPackages) {
|
||||
myRequiredModuleName = requiredModuleName;
|
||||
myImportedPackages = importedPackages;
|
||||
}
|
||||
|
||||
@@ -141,6 +138,13 @@ public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchIn
|
||||
return InspectionsBundle.message("inspection.redundant.requires.statement.fix.family");
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return InspectionsBundle.message("inspection.redundant.requires.statement.fix.name", myRequiredModuleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getPsiElement();
|
||||
@@ -209,55 +213,8 @@ public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchIn
|
||||
}
|
||||
}
|
||||
|
||||
private static class RedundantRequiresStatementVisitor extends RefJavaVisitor {
|
||||
private final InspectionManager myManager;
|
||||
private final ProblemDescriptionsProcessor myProblemDescriptionsProcessor;
|
||||
|
||||
public RedundantRequiresStatementVisitor(InspectionManager manager, ProblemDescriptionsProcessor problemDescriptionsProcessor) {
|
||||
myManager = manager;
|
||||
myProblemDescriptionsProcessor = problemDescriptionsProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJavaModule(@NotNull RefJavaModule refJavaModule) {
|
||||
super.visitJavaModule(refJavaModule);
|
||||
|
||||
RefModule refModule = refJavaModule.getModule();
|
||||
if (refModule != null) {
|
||||
Set<String> moduleImportedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
||||
if (moduleImportedPackages != null) {
|
||||
Map<String, Boolean> requiredModuleNames = refJavaModule.getRequiredModuleNames();
|
||||
if (!requiredModuleNames.isEmpty()) {
|
||||
Map<String, Map<String, List<String>>> exportedModulePackages = getExportedModulePackages(refJavaModule.getRefManager());
|
||||
for (String dependencyModuleName : requiredModuleNames.keySet()) {
|
||||
Map<String, List<String>> exportedPackages = exportedModulePackages.get(dependencyModuleName);
|
||||
if (exportedPackages != null && isDependencyUnused(exportedPackages, moduleImportedPackages, refJavaModule.getName())) {
|
||||
PsiRequiresStatement statement = refJavaModule.getRequiresStatements().get(dependencyModuleName);
|
||||
if (statement != null) {
|
||||
registerProblem(refJavaModule, statement, moduleImportedPackages, dependencyModuleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerProblem(@NotNull RefJavaModule refJavaModule,
|
||||
@NotNull PsiRequiresStatement requiresStatement,
|
||||
@NotNull Set<String> importedPackages,
|
||||
@NotNull String dependencyModuleName) {
|
||||
ProblemDescriptor descriptor =
|
||||
myManager.createProblemDescriptor(requiresStatement,
|
||||
InspectionsBundle
|
||||
.message("inspection.redundant.requires.statement.description", dependencyModuleName),
|
||||
new DeleteRedundantRequiresStatementFix(importedPackages),
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, false);
|
||||
myProblemDescriptionsProcessor.addProblemElement(refJavaModule, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RedundantRequiresStatementAnnotator extends RefGraphAnnotator {
|
||||
private static final Set<String> DONT_COLLECT_PACKAGES = Collections.emptySet();
|
||||
|
||||
@Override
|
||||
public void onReferencesBuild(RefElement refElement) {
|
||||
@@ -269,12 +226,9 @@ public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchIn
|
||||
}
|
||||
}
|
||||
else if (refElement instanceof RefJavaModule) {
|
||||
PsiJavaModule javaModule = ((RefJavaModule)refElement).getElement();
|
||||
if (javaModule != null) {
|
||||
RefModule refModule = refElement.getModule();
|
||||
if (refModule != null) {
|
||||
onJavaModuleReferencesBuilt(javaModule, refModule);
|
||||
}
|
||||
RefModule refModule = refElement.getModule();
|
||||
if (refModule != null) {
|
||||
setImportedPackages(refModule, refElement.getElement() != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,64 +237,54 @@ public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchIn
|
||||
if (file.getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) {
|
||||
PsiImportList importList = file.getImportList();
|
||||
if (importList != null) {
|
||||
Set<String> packageNames = new THashSet<>();
|
||||
PsiImportStatementBase[] statements = importList.getAllImportStatements();
|
||||
if (statements.length != 0) {
|
||||
for (PsiImportStatementBase statement : statements) {
|
||||
PsiElement resolved = statement.resolve();
|
||||
String packageName = null;
|
||||
if (resolved instanceof PsiPackage) {
|
||||
packageName = ((PsiPackage)resolved).getQualifiedName();
|
||||
}
|
||||
else if (resolved instanceof PsiMember) {
|
||||
PsiJavaFile parentFile = PsiTreeUtil.getParentOfType(resolved, PsiJavaFile.class);
|
||||
if (parentFile != null) {
|
||||
packageName = parentFile.getPackageName();
|
||||
RefModule refModule = refFile.getModule();
|
||||
if (refModule != null) {
|
||||
Set<String> packageNames = getImportedPackages(refModule);
|
||||
if (packageNames != DONT_COLLECT_PACKAGES) {
|
||||
PsiImportStatementBase[] statements = importList.getAllImportStatements();
|
||||
for (PsiImportStatementBase statement : statements) {
|
||||
String packageName = getPackageName(statement);
|
||||
if (!StringUtil.isEmpty(packageName)) {
|
||||
packageNames.add(packageName);
|
||||
}
|
||||
}
|
||||
if (!StringUtil.isEmpty(packageName)) {
|
||||
packageNames.add(packageName);
|
||||
}
|
||||
if (!packageNames.isEmpty()) {
|
||||
refFile.putUserData(IMPORTED_JAVA_PACKAGES, packageNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void onJavaModuleReferencesBuilt(@NotNull PsiJavaModule javaModule, @NotNull RefModule refModule) {
|
||||
LOG.assertTrue(refModule.getUserData(IMPORTED_JAVA_PACKAGES) == null, "Duplicate Java module declaration");
|
||||
refModule.putUserData(IMPORTED_JAVA_PACKAGES, new THashSet<>());
|
||||
|
||||
Map<String, Map<String, List<String>>> exportedModulePackages = getExportedModulePackages(refModule.getRefManager());
|
||||
for (PsiRequiresStatement statement : javaModule.getRequires()) {
|
||||
String dependencyModuleName = statement.getModuleName();
|
||||
if (dependencyModuleName != null) {
|
||||
Map<String, List<String>> exportedPackages = exportedModulePackages.get(dependencyModuleName);
|
||||
if (exportedPackages == null) {
|
||||
PsiJavaModule dependency = resolveRequiredModule(statement);
|
||||
exportedPackages = getExportedPackages(dependency);
|
||||
exportedModulePackages.put(dependencyModuleName, exportedPackages);
|
||||
}
|
||||
@Nullable
|
||||
private static String getPackageName(@NotNull PsiImportStatementBase statement) {
|
||||
PsiElement resolved = statement.resolve();
|
||||
if (resolved instanceof PsiPackage) {
|
||||
return ((PsiPackage)resolved).getQualifiedName();
|
||||
}
|
||||
else if (resolved instanceof PsiMember) {
|
||||
PsiJavaFile parentFile = PsiTreeUtil.getParentOfType(resolved, PsiJavaFile.class);
|
||||
if (parentFile != null) {
|
||||
return parentFile.getPackageName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<String, List<String>> getExportedPackages(@Nullable PsiJavaModule javaModule) {
|
||||
if (javaModule == null) {
|
||||
return Collections.emptyMap();
|
||||
private static Set<String> getImportedPackages(@NotNull RefModule refModule) {
|
||||
Set<String> importedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
||||
if (importedPackages == null) {
|
||||
PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByModule(refModule.getModule());
|
||||
importedPackages = javaModule != null ? new THashSet<>() : DONT_COLLECT_PACKAGES;
|
||||
refModule.putUserData(IMPORTED_JAVA_PACKAGES, importedPackages);
|
||||
}
|
||||
Map<String, List<String>> exportedPackages = new THashMap<>();
|
||||
for (PsiExportsStatement statement : javaModule.getExports()) {
|
||||
String packageName = statement.getPackageName();
|
||||
if (packageName != null) {
|
||||
exportedPackages.put(packageName, statement.getModuleNames());
|
||||
}
|
||||
return importedPackages;
|
||||
}
|
||||
|
||||
private static void setImportedPackages(RefModule refModule, boolean collectPackages) {
|
||||
Set<String> importedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
||||
if (importedPackages == null) {
|
||||
refModule.putUserData(IMPORTED_JAVA_PACKAGES, collectPackages ? new THashSet<>() : DONT_COLLECT_PACKAGES);
|
||||
}
|
||||
return !exportedPackages.isEmpty() ? exportedPackages : Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-23
@@ -34,9 +34,7 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule {
|
||||
private final RefModule myRefModule;
|
||||
|
||||
private Map<String, List<String>> myExportedPackageNames;
|
||||
private Map<String, Boolean> myRequiredModuleNames;
|
||||
|
||||
private Map<String, PsiRequiresStatement> myRequiresStatements;
|
||||
private Map<String, Dependency> myRequiredModules;
|
||||
|
||||
public RefJavaModuleImpl(@NotNull PsiJavaModule javaModule, @NotNull RefManagerImpl manager) {
|
||||
super(javaModule.getModuleName(), javaModule, manager);
|
||||
@@ -77,14 +75,8 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Boolean> getRequiredModuleNames() {
|
||||
return myRequiredModuleNames != null ? myRequiredModuleNames : Collections.emptyMap();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, PsiRequiresStatement> getRequiresStatements() {
|
||||
return myRequiresStatements != null ? myRequiresStatements : Collections.emptyMap();
|
||||
public Map<String, Dependency> getRequiredModules() {
|
||||
return myRequiredModules != null ? myRequiredModules : Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,31 +86,27 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule {
|
||||
for (PsiRequiresStatement statement : javaModule.getRequires()) {
|
||||
PsiJavaModuleReferenceElement referenceElement = statement.getReferenceElement();
|
||||
if (referenceElement != null) {
|
||||
if (myRequiresStatements == null) myRequiresStatements = new THashMap<>(1);
|
||||
myRequiresStatements.put(referenceElement.getReferenceText(), statement);
|
||||
|
||||
PsiPolyVariantReference moduleReference = referenceElement.getReference();
|
||||
PsiElement element = addReference(moduleReference);
|
||||
PsiElement element = addReference(referenceElement.getReference());
|
||||
if (element instanceof PsiJavaModule) {
|
||||
if (myRequiredModuleNames == null) myRequiredModuleNames = new THashMap<>(1);
|
||||
myRequiredModuleNames.put(((PsiJavaModule)element).getModuleName(), statement.isPublic());
|
||||
PsiJavaModule requiredModule = (PsiJavaModule)element;
|
||||
Map<String, List<String>> packageNames = getExportedPackagesSnapshot(requiredModule);
|
||||
if (myRequiredModules == null) myRequiredModules = new THashMap<>(1);
|
||||
myRequiredModules.put(requiredModule.getModuleName(), new Dependency(packageNames, statement.isPublic()));
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> emptyList = Collections.emptyList();
|
||||
for (PsiExportsStatement statement : javaModule.getExports()) {
|
||||
PsiJavaCodeReferenceElement packageReference = statement.getPackageReference();
|
||||
PsiElement element = addReference(packageReference);
|
||||
PsiElement element = addReference(statement.getPackageReference());
|
||||
String packageName = null;
|
||||
if (element instanceof PsiPackage) {
|
||||
if (myExportedPackageNames == null) myExportedPackageNames = new THashMap<>(1);
|
||||
packageName = ((PsiPackage)element).getQualifiedName();
|
||||
if (myExportedPackageNames == null) myExportedPackageNames = new THashMap<>(1);
|
||||
myExportedPackageNames.put(packageName, emptyList);
|
||||
}
|
||||
for (PsiJavaModuleReferenceElement referenceElement : statement.getModuleReferences()) {
|
||||
if (referenceElement != null) {
|
||||
PsiPolyVariantReference moduleReference = referenceElement.getReference();
|
||||
PsiElement moduleElement = addReference(moduleReference);
|
||||
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));
|
||||
@@ -150,4 +138,16 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule {
|
||||
}
|
||||
return resolvedElements.size() == 1 ? resolvedElements.get(0) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<String, List<String>> getExportedPackagesSnapshot(@NotNull PsiJavaModule javaModule) {
|
||||
Map<String, List<String>> exportedPackages = new THashMap<>();
|
||||
for (PsiExportsStatement statement : javaModule.getExports()) {
|
||||
String packageName = statement.getPackageName();
|
||||
if (packageName != null) {
|
||||
exportedPackages.put(packageName, statement.getModuleNames());
|
||||
}
|
||||
}
|
||||
return !exportedPackages.isEmpty() ? exportedPackages : Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,6 +471,7 @@ inspection.non.accessible.type.exposed.name=The class is not exported from the m
|
||||
inspection.redundant.requires.statement.name=Redundant 'requires' statement in module-info
|
||||
inspection.redundant.requires.statement.description=Redundant statement ''requires {0}''
|
||||
inspection.redundant.requires.statement.fix.family=Delete redundant 'requires' statement
|
||||
inspection.redundant.requires.statement.fix.name=Delete statement ''requires {0}''
|
||||
|
||||
inspection.root.node.title=Inspections
|
||||
|
||||
|
||||
Reference in New Issue
Block a user