diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java index 6869b610419f..462abf70f99d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java @@ -99,7 +99,7 @@ public final class JavaModuleGraphUtil { return CachedValuesManager.getCachedValue(rootPsi, () -> { VirtualFile _root = rootPsi.getVirtualFile(); LightJavaModule result = LightJavaModule.create(rootPsi.getManager(), _root, LightJavaModule.moduleName(_root)); - return Result.create(result, _root, ProjectRootModificationTracker.getInstance(rootPsi.getProject())); + return Result.create(result, _root, ProjectRootModificationTracker.getInstance(rootPsi.getProject())); }); } } @@ -169,7 +169,7 @@ public final class JavaModuleGraphUtil { List roots = new ArrayList<>(rootManager.getSourceRoots(resourceRootType)); roots.addAll(sourceRoots); files = ContainerUtil.mapNotNull(roots, root -> root.findFileByRelativePath(JarFile.MANIFEST_NAME)); - if (files.size() == 1) { + if (files.size() == 1 || new HashSet<>(files).size() == 1) { VirtualFile manifest = files.get(0); PsiFile manifestPsi = PsiManager.getInstance(project).findFile(manifest); assert manifestPsi != null : manifest; @@ -184,7 +184,8 @@ public final class JavaModuleGraphUtil { if (virtualAutoModuleName != null && !sourceSourceRoots.isEmpty()) { return LightJavaModule.create(PsiManager.getInstance(project), sourceSourceRoots.get(0), virtualAutoModuleName); } - } else { + } + else { final VirtualFile file = files.get(0); if (ContainerUtil.and(files, f -> f.equals(file))) { PsiFile psiFile = PsiManager.getInstance(project).findFile(file); @@ -257,7 +258,7 @@ public final class JavaModuleGraphUtil { } private static boolean isExported(@NotNull PsiJavaModule from, @NotNull PsiJavaModule to) { - VirtualFile toFile = to.getContainingFile().getVirtualFile(); + VirtualFile toFile = getVirtualFile(to); if (toFile == null) return false; Module fromModule = ModuleUtilCore.findModuleForPsiElement(from); @@ -276,6 +277,13 @@ public final class JavaModuleGraphUtil { return false; } + @Nullable + private static VirtualFile getVirtualFile(@NotNull PsiJavaModule module) { + if (module instanceof LightJavaModule light) { + return light.getRootVirtualFile(); + } + return PsiUtilCore.getVirtualFile(module); + } private static boolean alreadyContainsRequires(@NotNull PsiJavaModule module, @NotNull String dependency) { for (PsiRequiresStatement requiresStatement : module.getRequires()) { @@ -306,7 +314,7 @@ public final class JavaModuleGraphUtil { if (descriptors.size() == 2) { if (descriptors.stream() - .map(d -> PsiUtilCore.getVirtualFile(d)) + .map(d -> getVirtualFile(d)) .filter(Objects::nonNull) .map(moduleRootManager.getFileIndex()::isInTestSourceContent).count() < 2) { return Collections.emptyList(); diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/java19api/JavaEmptyModuleInfoFileInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/java19api/JavaEmptyModuleInfoFileInspection.java new file mode 100644 index 000000000000..64a6231b1022 --- /dev/null +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/java19api/JavaEmptyModuleInfoFileInspection.java @@ -0,0 +1,204 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInspection.java19api; + +import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil; +import com.intellij.codeInspection.*; +import com.intellij.java.JavaBundle; +import com.intellij.modcommand.ModPsiUpdater; +import com.intellij.modcommand.PsiUpdateModCommandQuickFix; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ContentIterator; +import com.intellij.openapi.roots.DependencyScope; +import com.intellij.openapi.roots.ModuleFileIndex; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.java.JavaFeature; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.intellij.psi.JavaTokenType.LBRACE; +import static com.intellij.psi.JavaTokenType.RBRACE; +import static com.intellij.psi.PsiJavaModule.MODULE_INFO_FILE; + +public class JavaEmptyModuleInfoFileInspection extends AbstractBaseJavaLocalInspectionTool { + private static final Set JVM_LANGUAGES = Set.of("java", "kt", "kts", "groovy"); + + @Override + public ProblemDescriptor @Nullable [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { + if (!PsiUtil.isAvailable(JavaFeature.MODULES, file)) return ProblemDescriptor.EMPTY_ARRAY; + if (!file.getName().equals(MODULE_INFO_FILE)) return ProblemDescriptor.EMPTY_ARRAY; + if (!(file instanceof PsiJavaFile javaFile)) return ProblemDescriptor.EMPTY_ARRAY; + + PsiJavaModule descriptor = javaFile.getModuleDeclaration(); + if (descriptor == null) return ProblemDescriptor.EMPTY_ARRAY; + + if (!isEmptyModule(descriptor)) return ProblemDescriptor.EMPTY_ARRAY; + if (!needRequires(descriptor)) return ProblemDescriptor.EMPTY_ARRAY; + + ProblemDescriptor problemDescriptor = manager.createProblemDescriptor( + file, + JavaBundle.message("inspection.unresolved.module.dependencies.problem.descriptor"), + isOnTheFly, + LocalQuickFix.notNullElements(new GenerateModuleInfoRequiresFix()), + ProblemHighlightType.WARNING + ); + return new ProblemDescriptor[]{problemDescriptor}; + } + + private static class GenerateModuleInfoRequiresFix extends PsiUpdateModCommandQuickFix { + @Override + public @NotNull String getFamilyName() { + return JavaBundle.message("inspection.auto.add.module.requirements.quickfix"); + } + + @Override + protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) { + if (!(element instanceof PsiJavaFile psiJavaFile)) return; + PsiJavaModule descriptor = psiJavaFile.getModuleDeclaration(); + if (descriptor == null) return; + Set modules = walk(descriptor, stmt -> true); + + if (modules.isEmpty()) { + PsiElement content = getStartContentElement(descriptor); + PsiElement newLine = PsiParserFacade.getInstance(element.getProject()) + .createWhiteSpaceFromText("\n"); + PsiComment comment = JavaPsiFacade.getElementFactory(element.getProject()) + .createCommentFromText("// no dependencies", null); + descriptor.addAfter(comment, content); + descriptor.addAfter(newLine, content); + } + else { + DependencyScope scope = getScope(descriptor); + for (PsiJavaModule target : modules) { + JavaModuleGraphUtil.addDependency(descriptor, target, scope); + } + } + } + } + + private static boolean isEmptyModule(@NotNull PsiJavaModule module) { + PsiElement element = getStartContentElement(module); + if (element == null) return false; + while ((element = element.getNextSibling()) != null) { + if (element.getNode().getElementType() == RBRACE) return true; + if (!(element instanceof PsiWhiteSpace)) return false; + } + return true; + } + + private static boolean needRequires(@NotNull PsiJavaModule descriptor) { + Set modules = walk(descriptor, psiJavaModule -> psiJavaModule.getName().equals(descriptor.getName())); + return !modules.isEmpty(); + } + + @Nullable + private static PsiElement getStartContentElement(@NotNull PsiJavaModule module) { + PsiElement[] children = module.getChildren(); + for (PsiElement child : children) { + if (child.getNode().getElementType() == LBRACE) { + return child; + } + } + return null; + } + + private static Set walk(@NotNull PsiJavaModule descriptor, + @NotNull Function<@NotNull PsiJavaModule, @NotNull Boolean> function) { + PsiFile descriptorFile = descriptor.getContainingFile().getOriginalFile(); + Module module = ModuleUtilCore.findModuleForFile(descriptorFile); + if (module == null) return Set.of(); + + PsiManager psiManager = PsiManager.getInstance(module.getProject()); + ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + ModuleFileIndex fileIndex = rootManager.getFileIndex(); + + // collect descriptors + Map imports = new ConcurrentHashMap<>(); + ImportsCollector collector = new ImportsCollector(psiManager, statement -> { + PsiJavaModule result = imports.computeIfAbsent(statement, stmt -> findDescriptor(stmt.resolve())); + return result == null || function.apply(result); + }); + DependencyScope scope = getScope(descriptor); + for (VirtualFile root : rootManager.getSourceRoots()) { + DependencyScope currentScope = fileIndex.isInTestSourceContent(root) ? DependencyScope.TEST : DependencyScope.COMPILE; + if (currentScope == scope) { + VfsUtilCore.iterateChildrenRecursively(root, file -> file.isDirectory() || + (file.getExtension() != null && JVM_LANGUAGES.contains(file.getExtension())), + collector); + } + } + + // clean descriptors + return imports.values().stream() + .filter(Objects::nonNull) + .filter(m -> !m.getName().equals(descriptor.getName())) + .collect(Collectors.toCollection(() -> new TreeSet<>((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())))); + } + + @NotNull + private static DependencyScope getScope(@NotNull PsiJavaModule descriptor) { + PsiFile file = descriptor.getContainingFile().getOriginalFile(); + Module module = ModuleUtilCore.findModuleForFile(file); + if (module == null) return DependencyScope.COMPILE; + + return ModuleRootManager.getInstance(module).getFileIndex() + .isInTestSourceContent(file.getVirtualFile()) + ? DependencyScope.TEST + : DependencyScope.COMPILE; + } + + @Nullable + private static PsiJavaModule findDescriptor(@Nullable PsiElement psiElement) { + if (psiElement == null) return null; + if (psiElement instanceof PsiPackage psiPackage) { + PsiDirectory[] directories = psiPackage.getDirectories(psiPackage.getResolveScope()); + for (PsiDirectory directory : directories) { + PsiJavaModule descriptor = JavaModuleGraphUtil.findDescriptorByElement(directory); + if (descriptor != null) return descriptor; + } + } + else { + return JavaModuleGraphUtil.findDescriptorByElement(psiElement); + } + return null; + } + + private static class ImportsCollector implements ContentIterator { + @NotNull + private final PsiManager myPsiManager; + @NotNull + private final Function myFunction; + + private ImportsCollector(@NotNull PsiManager manager, @NotNull Function function) { + myPsiManager = manager; + myFunction = function; + } + + @Override + public boolean processFile(@NotNull VirtualFile fileOrDir) { + PsiFile file = myPsiManager.findFile(fileOrDir); + if (file == null) return true; + if (file instanceof PsiJavaFile javaFile) { + PsiImportList imports = javaFile.getImportList(); + if (imports == null) return true; + for (PsiImportStatement importStatement : imports.getImportStatements()) { + if (!myFunction.apply(importStatement)) return false; + } + } + return true; + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 5c58cdc1923d..3dc91e138348 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1907,6 +1907,11 @@ enabledByDefault="true" level="WARNING" key="inspection.undeclared.service.usage.name" bundle="messages.JavaBundle" implementationClass="com.intellij.codeInspection.java19api.Java9UndeclaredServiceUsageInspection"/> + +Reports an empty module-info.java file, indicating unresolved module dependencies. Automatically adds necessary requires statements by inspecting imports. +To suppress this warning, include the following in module-info.java: +

+module module.name {
+    // no dependencies
+}
+
+Quick Fix: Auto-add module requirements fills in missing requires based on source code imports. +New in 2024.1 + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/lib/lib.jar b/java/java-tests/testData/inspection/emptyModuleInfoFile/lib/lib.jar new file mode 100644 index 000000000000..5b7ca99b6ad6 Binary files /dev/null and b/java/java-tests/testData/inspection/emptyModuleInfoFile/lib/lib.jar differ diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/lib/test.jar b/java/java-tests/testData/inspection/emptyModuleInfoFile/lib/test.jar new file mode 100644 index 000000000000..774bbb8f33b0 Binary files /dev/null and b/java/java-tests/testData/inspection/emptyModuleInfoFile/lib/test.jar differ diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/.idea/misc.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/.idea/misc.xml new file mode 100644 index 000000000000..07115cdf15dd --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/.idea/modules.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/.idea/modules.xml new file mode 100644 index 000000000000..cbdad6869bab --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/.idea/modules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/b.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/b.iml new file mode 100644 index 000000000000..3e0107b3e7ce --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/b.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/src/b/UtilB.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/src/b/UtilB.java new file mode 100644 index 000000000000..154cadcc90fb --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/src/b/UtilB.java @@ -0,0 +1,7 @@ +package b; + +public class UtilB { + public static String name() { + return "B"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/src/module-info.java new file mode 100644 index 000000000000..ed51eaf6adc5 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/b/src/module-info.java @@ -0,0 +1,3 @@ +module module.b { + exports b; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/c.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/c.iml new file mode 100644 index 000000000000..c90834f2d607 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/c.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/META-INF/MANIFEST.MF b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/META-INF/MANIFEST.MF new file mode 100644 index 000000000000..c753449d4be0 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Automatic-Module-Name: module.c diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/c/UtilC.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/c/UtilC.java new file mode 100644 index 000000000000..bdfd9c2d89e9 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/c/UtilC.java @@ -0,0 +1,7 @@ +package c; + +public class UtilC { + public static String name() { + return "C"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/module-info.java new file mode 100644 index 000000000000..c911b473ce4c --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/c/src/module-info.java @@ -0,0 +1,2 @@ +module module.c { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/d/d.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/d/d.iml new file mode 100644 index 000000000000..c90834f2d607 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/d/d.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/d/src/d/UtilD.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/d/src/d/UtilD.java new file mode 100644 index 000000000000..f6d133d4879d --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/d/src/d/UtilD.java @@ -0,0 +1,7 @@ +package d; + +public class UtilD { + public static String name() { + return "D"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/main.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/main.iml new file mode 100644 index 000000000000..c90834f2d607 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/src/a/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/src/a/Main.java new file mode 100644 index 000000000000..f24316f7e886 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/src/a/Main.java @@ -0,0 +1,11 @@ +package a; + +import b.*; +import c.*; +import d.*; +import l.*; + +public class Main { + public static void main(String[] args) { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/src/module-info.java new file mode 100644 index 000000000000..3aeed6870164 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/after/main/src/module-info.java @@ -0,0 +1,5 @@ +module module.a { + requires lib; + requires module.b; + requires module.c; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/.idea/misc.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/.idea/misc.xml new file mode 100644 index 000000000000..07115cdf15dd --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/.idea/modules.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/.idea/modules.xml new file mode 100644 index 000000000000..cbdad6869bab --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/.idea/modules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/b.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/b.iml new file mode 100644 index 000000000000..3e0107b3e7ce --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/b.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/src/b/UtilB.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/src/b/UtilB.java new file mode 100644 index 000000000000..154cadcc90fb --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/src/b/UtilB.java @@ -0,0 +1,7 @@ +package b; + +public class UtilB { + public static String name() { + return "B"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/src/module-info.java new file mode 100644 index 000000000000..ed51eaf6adc5 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/b/src/module-info.java @@ -0,0 +1,3 @@ +module module.b { + exports b; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/c.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/c.iml new file mode 100644 index 000000000000..c90834f2d607 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/c.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/src/META-INF/MANIFEST.MF b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/src/META-INF/MANIFEST.MF new file mode 100644 index 000000000000..c753449d4be0 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/src/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Automatic-Module-Name: module.c diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/src/c/UtilC.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/src/c/UtilC.java new file mode 100644 index 000000000000..bdfd9c2d89e9 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/c/src/c/UtilC.java @@ -0,0 +1,7 @@ +package c; + +public class UtilC { + public static String name() { + return "C"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/d/d.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/d/d.iml new file mode 100644 index 000000000000..c90834f2d607 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/d/d.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/d/src/d/UtilD.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/d/src/d/UtilD.java new file mode 100644 index 000000000000..f6d133d4879d --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/d/src/d/UtilD.java @@ -0,0 +1,7 @@ +package d; + +public class UtilD { + public static String name() { + return "D"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/main.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/main.iml new file mode 100644 index 000000000000..c90834f2d607 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/src/a/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/src/a/Main.java new file mode 100644 index 000000000000..f24316f7e886 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/src/a/Main.java @@ -0,0 +1,11 @@ +package a; + +import b.*; +import c.*; +import d.*; +import l.*; + +public class Main { + public static void main(String[] args) { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/src/module-info.java new file mode 100644 index 000000000000..d2a35b52798e --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/multiModuleProject/before/main/src/module-info.java @@ -0,0 +1,2 @@ +module module.a { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/.idea/misc.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/.idea/misc.xml new file mode 100644 index 000000000000..07115cdf15dd --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/.idea/modules.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/.idea/modules.xml new file mode 100644 index 000000000000..122a9054e101 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/main.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/main.iml new file mode 100644 index 000000000000..8b375d91007d --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/main.iml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/src/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/src/Main.java new file mode 100644 index 000000000000..7502db7b94c0 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/src/Main.java @@ -0,0 +1,7 @@ +import l.InLib; + +public class Main { + public static void main(String[] args) { + System.out.println(InLib.class); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/src/module-info.java new file mode 100644 index 000000000000..34f83275284a --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/src/module-info.java @@ -0,0 +1,3 @@ +module main { + requires lib; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/test/MainTest.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/test/MainTest.java new file mode 100644 index 000000000000..d1fc7b81f027 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/test/MainTest.java @@ -0,0 +1,7 @@ +import org.jetbrains.org.jetbrains.intellij.java.test.library.*; + +public class MainTest { + public static void main(String[] args) { + System.out.println(Util.name()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/test/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/test/module-info.java new file mode 100644 index 000000000000..f09df9a06253 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/after/test/module-info.java @@ -0,0 +1,3 @@ +module main { + requires intellij.java.test.library; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/.idea/misc.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/.idea/misc.xml new file mode 100644 index 000000000000..07115cdf15dd --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/.idea/modules.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/.idea/modules.xml new file mode 100644 index 000000000000..122a9054e101 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/main.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/main.iml new file mode 100644 index 000000000000..8b375d91007d --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/main.iml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/src/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/src/Main.java new file mode 100644 index 000000000000..7502db7b94c0 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/src/Main.java @@ -0,0 +1,7 @@ +import l.InLib; + +public class Main { + public static void main(String[] args) { + System.out.println(InLib.class); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/src/module-info.java new file mode 100644 index 000000000000..2bae5daaa7ad --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/src/module-info.java @@ -0,0 +1,2 @@ +module main { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/test/MainTest.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/test/MainTest.java new file mode 100644 index 000000000000..d1fc7b81f027 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/test/MainTest.java @@ -0,0 +1,7 @@ +import org.jetbrains.org.jetbrains.intellij.java.test.library.*; + +public class MainTest { + public static void main(String[] args) { + System.out.println(Util.name()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/test/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/test/module-info.java new file mode 100644 index 000000000000..2bae5daaa7ad --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleLibraryDependency/before/test/module-info.java @@ -0,0 +1,2 @@ +module main { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/.idea/misc.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/.idea/misc.xml new file mode 100644 index 000000000000..07115cdf15dd --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/.idea/modules.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/.idea/modules.xml new file mode 100644 index 000000000000..122a9054e101 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/main.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/main.iml new file mode 100644 index 000000000000..e5d2daf876a0 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/main.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/src/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/src/Main.java new file mode 100644 index 000000000000..6425aae3a311 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/src/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + System.out.println("test"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/src/module-info.java new file mode 100644 index 000000000000..eacfb691809c --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/src/module-info.java @@ -0,0 +1,2 @@ +module main { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/test/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/test/Main.java new file mode 100644 index 000000000000..6425aae3a311 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/test/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + System.out.println("test"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/test/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/test/module-info.java new file mode 100644 index 000000000000..eacfb691809c --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/after/test/module-info.java @@ -0,0 +1,2 @@ +module main { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/.idea/misc.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/.idea/misc.xml new file mode 100644 index 000000000000..07115cdf15dd --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/.idea/modules.xml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/.idea/modules.xml new file mode 100644 index 000000000000..122a9054e101 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/main.iml b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/main.iml new file mode 100644 index 000000000000..e5d2daf876a0 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/main.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/src/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/src/Main.java new file mode 100644 index 000000000000..6425aae3a311 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/src/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + System.out.println("test"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/src/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/src/module-info.java new file mode 100644 index 000000000000..eacfb691809c --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/src/module-info.java @@ -0,0 +1,2 @@ +module main { +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/test/Main.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/test/Main.java new file mode 100644 index 000000000000..6425aae3a311 --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/test/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + System.out.println("test"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/test/module-info.java b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/test/module-info.java new file mode 100644 index 000000000000..eacfb691809c --- /dev/null +++ b/java/java-tests/testData/inspection/emptyModuleInfoFile/singleModule/before/test/module-info.java @@ -0,0 +1,2 @@ +module main { +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/java19api/JavaEmptyModuleInfoFileInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/java19api/JavaEmptyModuleInfoFileInspectionTest.java new file mode 100644 index 000000000000..629b92fe086e --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/java19api/JavaEmptyModuleInfoFileInspectionTest.java @@ -0,0 +1,81 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInspection.java19api; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.java19api.JavaEmptyModuleInfoFileInspection; +import com.intellij.java.testFramework.fixtures.MultiModuleProjectDescriptor; +import com.intellij.openapi.application.impl.NonBlockingReadActionImpl; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.refactoring.LightMultiFileTestCase; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.util.LazyInitializer; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class JavaEmptyModuleInfoFileInspectionTest extends LightMultiFileTestCase { + private final LazyInitializer.LazyValue myDescriptor = new LazyInitializer.LazyValue<>(() -> { + MultiModuleProjectDescriptor value = + new MultiModuleProjectDescriptor(Paths.get(getTestDataPath() + "/" + getTestName(true)), "main", null); + Path lib = value.getProjectPath().getParent().getParent().resolve("lib"); + Path beforeLib = value.getBeforePath().getParent().getParent().resolve("lib"); + try { + FileUtilRt.deleteRecursively(lib); + FileUtil.copyDir(beforeLib.toFile(), lib.toFile()); + } + catch (IOException ignore) { + Assert.fail("Failed to copy lib files"); + } + return value; + }); + + @Override + protected @NotNull LightProjectDescriptor getProjectDescriptor() { + return myDescriptor.get(); + } + + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection/emptyModuleInfoFile"; + } + + public void testSingleLibraryDependency() { + doTest("src/module-info.java"); + doTest("test/module-info.java"); + } + + public void testSingleModule() { + doTest("src/module-info.java"); + doTest("test/module-info.java"); + } + + public void testMultiModuleProject() { + doTest("src/module-info.java", "main/"); + } + + private void doTest(@NotNull String path) { + doTest(path, ""); + } + + private void doTest(@NotNull String path, @NotNull String dir) { + VirtualFile file = getModule().getModuleFile().getParent().findFileByRelativePath(path); + myFixture.configureFromExistingVirtualFile(file); + + JavaEmptyModuleInfoFileInspection inspection = new JavaEmptyModuleInfoFileInspection(); + myFixture.enableInspections(inspection); + myFixture.testHighlighting(true, false, false, file); + + IntentionAction intention = myFixture.getAvailableIntention("Auto-add module requirements"); + if (intention != null) { + myFixture.launchAction(intention); + NonBlockingReadActionImpl.waitForAsyncTaskCompletion(); + } + myFixture.checkResultByFile(getTestName(false) + "/after/" + dir + path); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleProjectDescriptor.java b/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleProjectDescriptor.java index 780bce1a5ebd..c370adb0e04e 100644 --- a/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleProjectDescriptor.java +++ b/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleProjectDescriptor.java @@ -59,7 +59,7 @@ public class MultiModuleProjectDescriptor extends DefaultLightProjectDescriptor myPath = path; myMainModuleName = mainModuleName; myProcess = process; - myProjectPath = TemporaryDirectory.generateTemporaryPath(ProjectImpl.LIGHT_PROJECT_NAME); + myProjectPath = TemporaryDirectory.generateTemporaryPath("project/before/" + ProjectImpl.LIGHT_PROJECT_NAME); } public Path getBeforePath() { diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index dd63a6f023d7..bce9971aeed0 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -798,6 +798,9 @@ inspection.unused.symbol.check.localvars=Local variables inspection.unused.symbol.check.methods=Methods: inspection.unused.symbol.check.parameters=Parameters in inspection.unused.symbol.check.parameters.excluding.hierarchy=Excluding hierarchy +inspection.empty.module.info.file=Empty 'module-info.java' file +inspection.unresolved.module.dependencies.problem.descriptor=Unresolved module dependencies +inspection.auto.add.module.requirements.quickfix=Auto-add module requirements inspection.value.based.warnings=Value-based warnings inspection.preview.feature=Preview Feature warning inspection.value.based.warnings.synchronization=Attempt to synchronize on an instance of a value-based class