[java, inspection] generate necessary requires for empty module-info.java file. IDEA-341665

GitOrigin-RevId: 47bce7db3bc4429cdfd78bbb3518e8fea1cc80b8
This commit is contained in:
Aleksey Dobrynin
2024-02-16 11:31:15 +00:00
committed by intellij-monorepo-bot
parent f31f3f2c58
commit 1714ca86b3
64 changed files with 747 additions and 6 deletions
@@ -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<VirtualFile> 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();
@@ -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<String> 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<PsiJavaModule> 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<PsiJavaModule> 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<PsiJavaModule> 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<PsiImportStatement, PsiJavaModule> 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<PsiImportStatement, Boolean> myFunction;
private ImportsCollector(@NotNull PsiManager manager, @NotNull Function<PsiImportStatement, Boolean> 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;
}
}
}
@@ -1907,6 +1907,11 @@
enabledByDefault="true" level="WARNING"
key="inspection.undeclared.service.usage.name" bundle="messages.JavaBundle"
implementationClass="com.intellij.codeInspection.java19api.Java9UndeclaredServiceUsageInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="JavaEmptyModuleInfoFile"
groupBundle="messages.InspectionsBundle" groupKey="group.names.visibility.issues"
enabledByDefault="true" level="WARNING"
key="inspection.empty.module.info.file" bundle="messages.JavaBundle"
implementationClass="com.intellij.codeInspection.java19api.JavaEmptyModuleInfoFileInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantComparatorComparing"
groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
@@ -0,0 +1,11 @@
<body>
Reports an empty <code>module-info.java</code> file, indicating unresolved module dependencies. Automatically adds necessary <code>requires</code> statements by inspecting imports.
To suppress this warning, include the following in <code>module-info.java</code>:
<pre><code>
module module.name {
// no dependencies
}
</code></pre>
<b>Quick Fix:</b> <i>Auto-add module requirements</i> fills in missing <code>requires</code> based on source code imports.
<small>New in 2024.1</small>
</body>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main/main.iml" filepath="$PROJECT_DIR$/main/main.iml" />
<module fileurl="file://$PROJECT_DIR$/b/b.iml" filepath="$PROJECT_DIR$/b/b.iml" />
<module fileurl="file://$PROJECT_DIR$/c/c.iml" filepath="$PROJECT_DIR$/c/c.iml" />
<module fileurl="file://$PROJECT_DIR$/d/d.iml" filepath="$PROJECT_DIR$/d/d.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../../lib/lib.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
@@ -0,0 +1,7 @@
package b;
public class UtilB {
public static String name() {
return "B";
}
}
@@ -0,0 +1,3 @@
module module.b {
exports b;
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
Automatic-Module-Name: module.c
@@ -0,0 +1,7 @@
package c;
public class UtilC {
public static String name() {
return "C";
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,7 @@
package d;
public class UtilD {
public static String name() {
return "D";
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,11 @@
package a;
import b.*;
import c.*;
import d.*;
import l.*;
public class Main {
public static void main(String[] args) {
}
}
@@ -0,0 +1,5 @@
module module.a {
requires lib;
requires module.b;
requires module.c;
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main/main.iml" filepath="$PROJECT_DIR$/main/main.iml" />
<module fileurl="file://$PROJECT_DIR$/b/b.iml" filepath="$PROJECT_DIR$/b/b.iml" />
<module fileurl="file://$PROJECT_DIR$/c/c.iml" filepath="$PROJECT_DIR$/c/c.iml" />
<module fileurl="file://$PROJECT_DIR$/d/d.iml" filepath="$PROJECT_DIR$/d/d.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../../lib/lib.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
@@ -0,0 +1,7 @@
package b;
public class UtilB {
public static String name() {
return "B";
}
}
@@ -0,0 +1,3 @@
module module.b {
exports b;
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
Automatic-Module-Name: module.c
@@ -0,0 +1,7 @@
package c;
public class UtilC {
public static String name() {
return "C";
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,7 @@
package d;
public class UtilD {
public static String name() {
return "D";
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,11 @@
package a;
import b.*;
import c.*;
import d.*;
import l.*;
public class Main {
public static void main(String[] args) {
}
}
@@ -0,0 +1,2 @@
<warning descr="Unresolved module dependencies">module module.a {
}</warning>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../lib/lib.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../lib/test.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
@@ -0,0 +1,7 @@
import l.InLib;
public class Main {
public static void main(String[] args) {
System.out.println(InLib.class);
}
}
@@ -0,0 +1,3 @@
module main {
requires lib;
}
@@ -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());
}
}
@@ -0,0 +1,3 @@
module main {
requires intellij.java.test.library;
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../lib/lib.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../lib/test.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
@@ -0,0 +1,7 @@
import l.InLib;
public class Main {
public static void main(String[] args) {
System.out.println(InLib.class);
}
}
@@ -0,0 +1,2 @@
<warning descr="Unresolved module dependencies">module main {
}</warning>
@@ -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());
}
}
@@ -0,0 +1,2 @@
<warning descr="Unresolved module dependencies">module main {
}</warning>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,5 @@
public class Main {
public static void main(String[] args) {
System.out.println("test");
}
}
@@ -0,0 +1,5 @@
public class Main {
public static void main(String[] args) {
System.out.println("test");
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,5 @@
public class Main {
public static void main(String[] args) {
System.out.println("test");
}
}
@@ -0,0 +1,5 @@
public class Main {
public static void main(String[] args) {
System.out.println("test");
}
}
@@ -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<MultiModuleProjectDescriptor> 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);
}
}
@@ -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() {
@@ -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