mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] more module-related problems migrated
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: e0b7381612b50ee09c7a5ff676aec5f14da6636d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6fc7a8b6e5
commit
5d47c60541
@@ -3,6 +3,7 @@ package com.intellij.java.codeserver.core;
|
||||
|
||||
import com.intellij.ide.highlighter.ArchiveFileType;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
@@ -22,14 +23,17 @@ import com.intellij.psi.search.searches.JavaModuleSearch;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.JavaMultiReleaseUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.graph.DFSTBuilder;
|
||||
import com.intellij.util.graph.Graph;
|
||||
import com.intellij.util.graph.GraphGenerator;
|
||||
import com.intellij.util.indexing.DumbModeAccessType;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jetbrains.jps.model.java.JavaResourceRootType;
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType;
|
||||
|
||||
@@ -38,6 +42,7 @@ import java.util.function.BiFunction;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import static com.intellij.psi.PsiJavaModule.JAVA_BASE;
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
/**
|
||||
* Utilities related to JPMS modules
|
||||
@@ -243,6 +248,80 @@ public final class JavaPsiModuleUtil {
|
||||
return getRequiresGraph(source).getAllDependencies(source, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param module module to check for dependency cycles
|
||||
* @return collection of modules that form a dependency cycle
|
||||
*/
|
||||
public static @NotNull Collection<PsiJavaModule> findCycle(@NotNull PsiJavaModule module) {
|
||||
Project project = module.getProject();
|
||||
List<Set<PsiJavaModule>> cycles = CachedValuesManager.getManager(project).getCachedValue(project, () ->
|
||||
CachedValueProvider.Result.create(findCycles(project),
|
||||
PsiJavaModuleModificationTracker.getInstance(project),
|
||||
ProjectRootModificationTracker.getInstance(project)));
|
||||
return requireNonNullElse(ContainerUtil.find(cycles, set -> set.contains(module)), Collections.emptyList());
|
||||
}
|
||||
|
||||
private static @Nullable VirtualFile getVirtualFile(@NotNull PsiJavaModule module) {
|
||||
if (module instanceof LightJavaModule light) {
|
||||
return light.getRootVirtualFile();
|
||||
}
|
||||
return PsiUtilCore.getVirtualFile(module);
|
||||
}
|
||||
|
||||
/*
|
||||
* Looks for cycles between Java modules in the project sources.
|
||||
* Library/JDK modules are excluded in an assumption there can't be any lib -> src dependencies.
|
||||
* Module references are resolved "globally" (i.e., without taking project dependencies into account).
|
||||
*/
|
||||
private static @Unmodifiable @NotNull List<Set<PsiJavaModule>> findCycles(@NotNull Project project) {
|
||||
Set<PsiJavaModule> projectModules = new HashSet<>();
|
||||
for (Module module : ModuleManager.getInstance(project).getModules()) {
|
||||
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
|
||||
List<PsiJavaModule> descriptors = ContainerUtil.mapNotNull(moduleRootManager.getSourceRoots(true),
|
||||
root -> findDescriptorByFile(root, project));
|
||||
if (descriptors.size() > 2) return Collections.emptyList(); // aborts the process when there are incorrect modules in the project
|
||||
|
||||
if (descriptors.size() == 2) {
|
||||
if (descriptors.stream()
|
||||
.map(d -> getVirtualFile(d))
|
||||
.filter(Objects::nonNull).count() < 2) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
projectModules.addAll(descriptors);
|
||||
}
|
||||
if (descriptors.size() == 1) projectModules.add(descriptors.get(0));
|
||||
}
|
||||
|
||||
if (!projectModules.isEmpty()) {
|
||||
MultiMap<PsiJavaModule, PsiJavaModule> relations = MultiMap.create();
|
||||
for (PsiJavaModule module : projectModules) {
|
||||
for (PsiRequiresStatement statement : module.getRequires()) {
|
||||
PsiJavaModuleReference ref = statement.getModuleReference();
|
||||
if (ref != null) {
|
||||
ResolveResult[] results = ref.multiResolve(true);
|
||||
if (results.length == 1) {
|
||||
PsiJavaModule dependency = (PsiJavaModule)results[0].getElement();
|
||||
if (dependency != null && projectModules.contains(dependency)) {
|
||||
relations.putValue(module, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!relations.isEmpty()) {
|
||||
Graph<PsiJavaModule> graph = new ChameleonGraph<>(relations, false);
|
||||
DFSTBuilder<PsiJavaModule> builder = new DFSTBuilder<>(graph);
|
||||
Collection<Collection<PsiJavaModule>> components = builder.getComponents();
|
||||
if (!components.isEmpty()) {
|
||||
return ContainerUtil.map(components, elements -> new LinkedHashSet<>(elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a dependency conflict when a single package is imported from two modules
|
||||
* @param packageName package name
|
||||
|
||||
@@ -492,3 +492,9 @@ module.duplicate.uses=Duplicate ''uses'': {0}
|
||||
module.duplicate.provides=Duplicate ''provides'': {0}
|
||||
module.file.wrong.location=Module declaration should be located in a module's source root
|
||||
module.opens.in.weak.module='opens' is not allowed in an open module
|
||||
module.service.enum=The service definition is an enum: {0}
|
||||
module.not.found=Module not found: {0}
|
||||
module.not.on.path=Module is not in dependencies: {0}
|
||||
module.cyclic.dependence=Cyclic dependence: {0}
|
||||
module.duplicate.exports.target=Duplicate ''exports'' target: {0}
|
||||
module.duplicate.opens.target=Duplicate ''opens'' target: {0}
|
||||
|
||||
+10
@@ -642,6 +642,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
super.visitRequiresStatement(statement);
|
||||
if (JavaFeature.MODULES.isSufficient(myLanguageLevel)) {
|
||||
if (!hasErrorResults() && myLanguageLevel.isAtLeast(LanguageLevel.JDK_10)) myModuleChecker.checkModifiers(statement);
|
||||
if (!hasErrorResults()) myModuleChecker.checkModuleReference(statement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,6 +651,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
super.visitPackageAccessibilityStatement(statement);
|
||||
if (JavaFeature.MODULES.isSufficient(myLanguageLevel)) {
|
||||
if (!hasErrorResults()) myModuleChecker.checkHostModuleStrength(statement);
|
||||
if (!hasErrorResults()) myModuleChecker.checkDuplicateModuleReferences(statement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,6 +661,14 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
if (!hasErrorResults()) checkPreviewFeature(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUsesStatement(@NotNull PsiUsesStatement statement) {
|
||||
super.visitUsesStatement(statement);
|
||||
if (JavaFeature.MODULES.isSufficient(myLanguageLevel)) {
|
||||
if (!hasErrorResults()) myModuleChecker.checkServiceReference(statement.getClassReference());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitImportModuleStatement(@NotNull PsiImportModuleStatement statement) {
|
||||
super.visitImportModuleStatement(statement);
|
||||
|
||||
+64
-1
@@ -17,6 +17,7 @@ import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
|
||||
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
|
||||
|
||||
@@ -127,6 +128,20 @@ final class ModuleChecker {
|
||||
}
|
||||
}
|
||||
|
||||
void checkDuplicateModuleReferences(@NotNull PsiPackageAccessibilityStatement statement) {
|
||||
Set<String> targets = new HashSet<>();
|
||||
for (PsiJavaModuleReferenceElement refElement : statement.getModuleReferences()) {
|
||||
String refText = refElement.getReferenceText();
|
||||
PsiJavaModuleReference ref = refElement.getReference();
|
||||
assert ref != null : statement;
|
||||
if (!targets.add(refText)) {
|
||||
boolean exports = statement.getRole() == PsiPackageAccessibilityStatement.Role.EXPORTS;
|
||||
var kind = exports ? JavaErrorKinds.MODULE_DUPLICATE_EXPORTS_TARGET : JavaErrorKinds.MODULE_DUPLICATE_OPENS_TARGET;
|
||||
myVisitor.report(kind.create(refElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String qName(PsiJavaCodeReferenceElement ref) {
|
||||
return ref != null ? ref.getQualifiedName() : null;
|
||||
}
|
||||
@@ -167,4 +182,52 @@ final class ModuleChecker {
|
||||
myVisitor.report(JavaErrorKinds.MODULE_OPENS_IN_WEAK_MODULE.create(statement, module));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkServiceReference(@Nullable PsiJavaCodeReferenceElement refElement) {
|
||||
if (refElement != null) {
|
||||
PsiElement target = refElement.resolve();
|
||||
if (!(target instanceof PsiClass psiClass)) {
|
||||
myVisitor.report(JavaErrorKinds.REFERENCE_UNRESOLVED.create(refElement));
|
||||
}
|
||||
else if (psiClass.isEnum()) {
|
||||
myVisitor.report(JavaErrorKinds.MODULE_SERVICE_ENUM.create(refElement, psiClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkModuleReference(@NotNull PsiRequiresStatement statement) {
|
||||
PsiJavaModuleReferenceElement refElement = statement.getReferenceElement();
|
||||
if (refElement != null) {
|
||||
PsiJavaModuleReference ref = refElement.getReference();
|
||||
assert ref != null : refElement.getParent();
|
||||
PsiJavaModule target = ref.resolve();
|
||||
if (target == null) {
|
||||
reportUnresolvedJavaModule(refElement);
|
||||
return;
|
||||
}
|
||||
PsiJavaModule container = (PsiJavaModule)statement.getParent();
|
||||
if (target == container) {
|
||||
myVisitor.report(JavaErrorKinds.MODULE_CYCLIC_DEPENDENCE.create(refElement, Set.of(container)));
|
||||
}
|
||||
else {
|
||||
Collection<PsiJavaModule> cycle = JavaPsiModuleUtil.findCycle(target);
|
||||
if (cycle.contains(container)) {
|
||||
myVisitor.report(JavaErrorKinds.MODULE_CYCLIC_DEPENDENCE.create(refElement, cycle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reportUnresolvedJavaModule(@NotNull PsiJavaModuleReferenceElement refElement) {
|
||||
PsiJavaModuleReference ref = refElement.getReference();
|
||||
assert ref != null : refElement.getParent();
|
||||
|
||||
ResolveResult[] results = ref.multiResolve(true);
|
||||
switch (results.length) {
|
||||
case 0 -> myVisitor.report(myVisitor.isIncompleteModel()
|
||||
? JavaErrorKinds.REFERENCE_PENDING.create(refElement)
|
||||
: JavaErrorKinds.MODULE_NOT_FOUND.create(refElement));
|
||||
case 1 -> myVisitor.report(JavaErrorKinds.MODULE_NOT_ON_PATH.create(refElement));
|
||||
default -> {}
|
||||
}
|
||||
}}
|
||||
|
||||
+24
@@ -1373,6 +1373,30 @@ public final class JavaErrorKinds {
|
||||
.withRange((module, file) -> getRange(module));
|
||||
public static final Parameterized<PsiPackageAccessibilityStatement, PsiJavaModule> MODULE_OPENS_IN_WEAK_MODULE =
|
||||
parameterized(PsiPackageAccessibilityStatement.class, PsiJavaModule.class, "module.opens.in.weak.module");
|
||||
public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> MODULE_SERVICE_ENUM =
|
||||
parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "module.service.enum")
|
||||
.withAnchor(ref -> requireNonNullElse(ref.getReferenceNameElement(), ref))
|
||||
.withRawDescription((ref, cls) -> message("module.service.enum", cls.getName()));
|
||||
public static final Simple<PsiJavaModuleReferenceElement> MODULE_NOT_FOUND =
|
||||
error(PsiJavaModuleReferenceElement.class, "module.not.found")
|
||||
.withHighlightType(JavaErrorHighlightType.WRONG_REF)
|
||||
.withRawDescription(ref -> message("module.not.found", ref.getReferenceText()));
|
||||
public static final Simple<PsiJavaModuleReferenceElement> MODULE_NOT_ON_PATH =
|
||||
error(PsiJavaModuleReferenceElement.class, "module.not.on.path")
|
||||
.withRawDescription(ref -> message("module.not.on.path", ref.getReferenceText()));
|
||||
public static final Parameterized<PsiJavaModuleReferenceElement, Collection<PsiJavaModule>> MODULE_CYCLIC_DEPENDENCE =
|
||||
error(PsiJavaModuleReferenceElement.class, "module.cyclic.dependence")
|
||||
.<Collection<PsiJavaModule>>parameterized()
|
||||
.withRawDescription(
|
||||
(ref, modules) -> message("module.cyclic.dependence", modules.stream().map(PsiJavaModule::getName)
|
||||
.sorted().collect(Collectors.joining(", "))));
|
||||
|
||||
public static final Simple<PsiJavaModuleReferenceElement> MODULE_DUPLICATE_EXPORTS_TARGET =
|
||||
error(PsiJavaModuleReferenceElement.class, "module.duplicate.exports.target")
|
||||
.withRawDescription(ref -> message("module.duplicate.exports.target", ref.getReferenceText()));
|
||||
public static final Simple<PsiJavaModuleReferenceElement> MODULE_DUPLICATE_OPENS_TARGET =
|
||||
error(PsiJavaModuleReferenceElement.class, "module.duplicate.opens.target")
|
||||
.withRawDescription(ref -> message("module.duplicate.opens.target", ref.getReferenceText()));
|
||||
|
||||
private static @NotNull <Psi extends PsiElement> Simple<Psi> error(
|
||||
@NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) {
|
||||
|
||||
+13
-9
@@ -426,7 +426,19 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
super.visitPackageAccessibilityStatement(statement);
|
||||
if (JavaFeature.MODULES.isSufficient(myLanguageLevel)) {
|
||||
if (!hasErrorResults()) add(ModuleHighlightUtil.checkPackageReference(statement, myFile));
|
||||
if (!hasErrorResults()) ModuleHighlightUtil.checkPackageAccessTargets(statement, myErrorSink);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitModuleReferenceElement(@NotNull PsiJavaModuleReferenceElement refElement) {
|
||||
super.visitModuleReferenceElement(refElement);
|
||||
PsiJavaModuleReference ref = refElement.getReference();
|
||||
if (refElement.getParent() instanceof PsiPackageAccessibilityStatement &&
|
||||
ref != null && ref.multiResolve(true).length == 0) {
|
||||
String message = JavaErrorBundle.message("module.not.found", refElement.getReferenceText());
|
||||
HighlightInfo.Builder info =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).descriptionAndTooltip(message);
|
||||
add(info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,14 +450,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUsesStatement(@NotNull PsiUsesStatement statement) {
|
||||
super.visitUsesStatement(statement);
|
||||
if (JavaFeature.MODULES.isSufficient(myLanguageLevel)) {
|
||||
if (!hasErrorResults()) add(ModuleHighlightUtil.checkServiceReference(statement.getClassReference()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitProvidesStatement(@NotNull PsiProvidesStatement statement) {
|
||||
super.visitProvidesStatement(statement);
|
||||
|
||||
+13
@@ -148,6 +148,19 @@ final class JavaErrorFixProvider {
|
||||
fix(MODULE_FILE_WRONG_LOCATION, error -> new MoveFileFix(error.psi().getContainingFile().getVirtualFile(),
|
||||
error.context(), QuickFixBundle.message("move.file.to.source.root.text")));
|
||||
fix(MODULE_OPENS_IN_WEAK_MODULE, error -> removeModifierFix(error.context(), PsiModifier.OPEN));
|
||||
fix(MODULE_DUPLICATE_EXPORTS_TARGET,
|
||||
error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.reference.fix.text")));
|
||||
fix(MODULE_DUPLICATE_OPENS_TARGET,
|
||||
error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.reference.fix.text")));
|
||||
multi(MODULE_NOT_ON_PATH, error -> {
|
||||
PsiJavaModuleReference ref = error.psi().getReference();
|
||||
if (ref != null) {
|
||||
List<IntentionAction> registrar = new ArrayList<>();
|
||||
myFactory.registerOrderEntryFixes(ref, registrar);
|
||||
return registrar;
|
||||
}
|
||||
return List.of();
|
||||
});
|
||||
}
|
||||
|
||||
private void createStatementFixes() {
|
||||
|
||||
-100
@@ -11,7 +11,6 @@ import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry;
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.*;
|
||||
@@ -19,7 +18,6 @@ import com.intellij.openapi.roots.libraries.Library;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiJavaModuleModificationTracker;
|
||||
import com.intellij.psi.impl.light.LightJavaModule;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
@@ -30,18 +28,14 @@ import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.graph.DFSTBuilder;
|
||||
import com.intellij.util.graph.Graph;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.openapi.roots.DependencyScope.PROVIDED;
|
||||
import static com.intellij.psi.PsiJavaModule.JAVA_BASE;
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
public final class JavaModuleGraphUtil {
|
||||
private static final Set<String> STATIC_REQUIRES_MODULE_NAMES = Set.of("lombok");
|
||||
@@ -72,15 +66,6 @@ public final class JavaModuleGraphUtil {
|
||||
return javaModule instanceof LightJavaModule ? null : javaModule;
|
||||
}
|
||||
|
||||
public static @NotNull Collection<PsiJavaModule> findCycle(@NotNull PsiJavaModule module) {
|
||||
Project project = module.getProject();
|
||||
List<Set<PsiJavaModule>> cycles = CachedValuesManager.getManager(project).getCachedValue(project, () ->
|
||||
Result.create(findCycles(project),
|
||||
PsiJavaModuleModificationTracker.getInstance(project),
|
||||
ProjectRootModificationTracker.getInstance(project)));
|
||||
return requireNonNullElse(ContainerUtil.find(cycles, set -> set.contains(module)), Collections.emptyList());
|
||||
}
|
||||
|
||||
public static boolean exports(@NotNull PsiJavaModule source, @NotNull String packageName, @Nullable PsiJavaModule target) {
|
||||
Map<String, Set<String>> exports = CachedValuesManager.getCachedValue(source, () ->
|
||||
Result.create(exportsMap(source), source.getContainingFile()));
|
||||
@@ -208,60 +193,6 @@ public final class JavaModuleGraphUtil {
|
||||
return scope == PROVIDED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Looks for cycles between Java modules in the project sources.
|
||||
* Library/JDK modules are excluded in an assumption there can't be any lib -> src dependencies.
|
||||
* Module references are resolved "globally" (i.e., without taking project dependencies into account).
|
||||
*/
|
||||
private static @Unmodifiable @NotNull List<Set<PsiJavaModule>> findCycles(@NotNull Project project) {
|
||||
Set<PsiJavaModule> projectModules = new HashSet<>();
|
||||
for (Module module : ModuleManager.getInstance(project).getModules()) {
|
||||
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
|
||||
List<PsiJavaModule> descriptors = ContainerUtil.mapNotNull(moduleRootManager.getSourceRoots(true),
|
||||
root -> findDescriptorByFile(root, project));
|
||||
if (descriptors.size() > 2) return Collections.emptyList(); // aborts the process when there are incorrect modules in the project
|
||||
|
||||
if (descriptors.size() == 2) {
|
||||
if (descriptors.stream()
|
||||
.map(d -> getVirtualFile(d))
|
||||
.filter(Objects::nonNull).count() < 2) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
projectModules.addAll(descriptors);
|
||||
}
|
||||
if (descriptors.size() == 1) projectModules.add(descriptors.get(0));
|
||||
}
|
||||
|
||||
if (!projectModules.isEmpty()) {
|
||||
MultiMap<PsiJavaModule, PsiJavaModule> relations = MultiMap.create();
|
||||
for (PsiJavaModule module : projectModules) {
|
||||
for (PsiRequiresStatement statement : module.getRequires()) {
|
||||
PsiJavaModuleReference ref = statement.getModuleReference();
|
||||
if (ref != null) {
|
||||
ResolveResult[] results = ref.multiResolve(true);
|
||||
if (results.length == 1) {
|
||||
PsiJavaModule dependency = (PsiJavaModule)results[0].getElement();
|
||||
if (dependency != null && projectModules.contains(dependency)) {
|
||||
relations.putValue(module, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!relations.isEmpty()) {
|
||||
Graph<PsiJavaModule> graph = new ChameleonGraph<>(relations, false);
|
||||
DFSTBuilder<PsiJavaModule> builder = new DFSTBuilder<>(graph);
|
||||
Collection<Collection<PsiJavaModule>> components = builder.getComponents();
|
||||
if (!components.isEmpty()) {
|
||||
return ContainerUtil.map(components, elements -> new LinkedHashSet<>(elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static @NotNull Map<String, Set<String>> exportsMap(@NotNull PsiJavaModule source) {
|
||||
Map<String, Set<String>> map = new HashMap<>();
|
||||
for (PsiPackageAccessibilityStatement statement : source.getExports()) {
|
||||
@@ -272,37 +203,6 @@ public final class JavaModuleGraphUtil {
|
||||
return map;
|
||||
}
|
||||
|
||||
private static final class ChameleonGraph<N> implements Graph<N> {
|
||||
private final Set<N> myNodes;
|
||||
private final MultiMap<N, N> myEdges;
|
||||
private final boolean myInbound;
|
||||
|
||||
private ChameleonGraph(MultiMap<N, N> edges, boolean inbound) {
|
||||
myNodes = new HashSet<>();
|
||||
edges.entrySet().forEach(e -> {
|
||||
myNodes.add(e.getKey());
|
||||
myNodes.addAll(e.getValue());
|
||||
});
|
||||
myEdges = edges;
|
||||
myInbound = inbound;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<N> getNodes() {
|
||||
return myNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<N> getIn(N n) {
|
||||
return myInbound ? myEdges.get(n).iterator() : Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<N> getOut(N n) {
|
||||
return myInbound ? Collections.emptyIterator() : myEdges.get(n).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public static class JavaModuleScope extends GlobalSearchScope {
|
||||
private final @NotNull MultiMap<String, PsiJavaModule> myModules;
|
||||
private final boolean myIncludeLibraries;
|
||||
|
||||
+10
-55
@@ -13,7 +13,6 @@ import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.java.codeserver.core.JavaServiceProviderUtil;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
@@ -30,12 +29,9 @@ import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
// generates HighlightInfoType.ERROR-like HighlightInfos for modularity-related (Jigsaw) problems
|
||||
final class ModuleHighlightUtil {
|
||||
@@ -102,19 +98,16 @@ final class ModuleHighlightUtil {
|
||||
PsiJavaModuleReference ref = refElement.getReference();
|
||||
assert ref != null : refElement.getParent();
|
||||
PsiJavaModule target = ref.resolve();
|
||||
if (target == null) return getUnresolvedJavaModuleReason(statement, refElement);
|
||||
PsiJavaModule container = (PsiJavaModule)statement.getParent();
|
||||
if (target == container) {
|
||||
String message = JavaErrorBundle.message("module.cyclic.dependence", container.getName());
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).descriptionAndTooltip(message);
|
||||
}
|
||||
else {
|
||||
Collection<PsiJavaModule> cycle = JavaModuleGraphUtil.findCycle(target);
|
||||
if (cycle.contains(container)) {
|
||||
Stream<String> stream = cycle.stream().map(PsiJavaModule::getName);
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) stream = stream.sorted();
|
||||
String message = JavaErrorBundle.message("module.cyclic.dependence", stream.collect(Collectors.joining(", ")));
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).descriptionAndTooltip(message);
|
||||
if (target == null) {
|
||||
PsiJavaModuleReference ref1 = refElement.getReference();
|
||||
assert ref1 != null : refElement.getParent();
|
||||
|
||||
ResolveResult[] results = ref1.multiResolve(true);
|
||||
if (results.length > 1) {
|
||||
// TODO: make as error or extract to inspection
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING)
|
||||
.range(refElement)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("module.ambiguous", refElement.getReferenceText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,44 +196,6 @@ final class ModuleHighlightUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static void checkPackageAccessTargets(@NotNull PsiPackageAccessibilityStatement statement,
|
||||
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
|
||||
Set<String> targets = new HashSet<>();
|
||||
for (PsiJavaModuleReferenceElement refElement : statement.getModuleReferences()) {
|
||||
String refText = refElement.getReferenceText();
|
||||
PsiJavaModuleReference ref = refElement.getReference();
|
||||
assert ref != null : statement;
|
||||
if (!targets.add(refText)) {
|
||||
boolean exports = statement.getRole() == Role.EXPORTS;
|
||||
String message = JavaErrorBundle.message(exports ? "module.duplicate.exports.target" : "module.duplicate.opens.target", refText);
|
||||
HighlightInfo.Builder info = createDuplicateReference(refElement, message);
|
||||
errorSink.accept(info);
|
||||
}
|
||||
else if (ref.multiResolve(true).length == 0) {
|
||||
String message = JavaErrorBundle.message("module.not.found", refElement.getReferenceText());
|
||||
HighlightInfo.Builder info =
|
||||
HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).descriptionAndTooltip(message);
|
||||
errorSink.accept(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkServiceReference(@Nullable PsiJavaCodeReferenceElement refElement) {
|
||||
if (refElement != null) {
|
||||
PsiElement target = refElement.resolve();
|
||||
if (!(target instanceof PsiClass psiClass)) {
|
||||
String message = JavaErrorBundle.message("cannot.resolve.symbol", refElement.getReferenceName());
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(refElement)).descriptionAndTooltip(message);
|
||||
}
|
||||
else if (psiClass.isEnum()) {
|
||||
String message = JavaErrorBundle.message("module.service.enum", psiClass.getName());
|
||||
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(refElement)).descriptionAndTooltip(message);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void checkServiceImplementations(@NotNull PsiProvidesStatement statement, @NotNull PsiFile file,
|
||||
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
|
||||
PsiReferenceList implRefList = statement.getImplementationList();
|
||||
|
||||
Reference in New Issue
Block a user