diff --git a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java index cb055cea3325..6839a97daaad 100644 --- a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java +++ b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java @@ -4,6 +4,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.module.ModuleUtilCore; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; @@ -20,10 +21,8 @@ import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; 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.psi.util.*; +import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.graph.DFSTBuilder; @@ -522,6 +521,67 @@ public final class JavaPsiModuleUtil { } } + /** + * State of package reference in module-info file + */ + public enum PackageReferenceState { + /** + * Valid reference to a non-empty package + */ + VALID, + /** + * No package is found + */ + PACKAGE_NOT_FOUND, + /** + * Package exists but contains no classes (for exports) or no files (for opens) + */ + PACKAGE_EMPTY + } + + /** + * @param statement statement to check ('opens' or 'exports' statement) + * @return state of the reference + */ + public static @NotNull PackageReferenceState checkPackageReference(@NotNull PsiPackageAccessibilityStatement statement) { + PsiJavaCodeReferenceElement refElement = statement.getPackageReference(); + if (refElement != null) { + PsiFile file = statement.getContainingFile(); + Module module = ModuleUtilCore.findModuleForFile(file); + if (module != null) { + PsiElement target = refElement.resolve(); + PsiDirectory[] directories = PsiDirectory.EMPTY_ARRAY; + if (target instanceof PsiPackage psiPackage) { + boolean inTests = ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(file.getVirtualFile()); + directories = psiPackage.getDirectories(module.getModuleScope(inTests)); + Module mainMultiReleaseModule = JavaMultiReleaseUtil.getMainMultiReleaseModule(module); + if (mainMultiReleaseModule != null) { + directories = ArrayUtil.mergeArrays(directories, psiPackage.getDirectories(mainMultiReleaseModule.getModuleScope(inTests))); + } + } + String packageName = statement.getPackageName(); + if (directories.length == 0) { + return PackageReferenceState.PACKAGE_NOT_FOUND; + } + boolean opens = statement.getRole() == PsiPackageAccessibilityStatement.Role.OPENS; + if (packageName != null && isPackageEmpty(directories, packageName, opens)) { + return PackageReferenceState.PACKAGE_EMPTY; + } + } + } + + return PackageReferenceState.VALID; + } + + private static boolean isPackageEmpty(PsiDirectory @NotNull [] directories, @NotNull String packageName, boolean anyFile) { + if (anyFile) { + return !ContainerUtil.exists(directories, dir -> dir.getFiles().length > 0); + } + else { + return PsiUtil.isPackageEmpty(directories, packageName); + } + } + /** * Helper service to support resolve in java-psi-impl */ diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 812f21da5f90..5d6f56505f02 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -514,3 +514,5 @@ module.cyclic.dependence=Cyclic dependency: {0} module.duplicate.exports.target=Duplicate ''exports'' target: {0} module.duplicate.opens.target=Duplicate ''opens'' target: {0} module.duplicate.implementation=Duplicate implementation: {0} +module.reference.package.not.found=Package not found: {0} +module.reference.package.empty=Package is empty: {0} diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index 0f79ac1f2a7c..bc21fb7c91b4 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -664,6 +664,7 @@ final class JavaErrorVisitor extends JavaElementVisitor { if (isApplicable(JavaFeature.MODULES)) { if (!hasErrorResults()) myModuleChecker.checkHostModuleStrength(statement); if (!hasErrorResults()) myModuleChecker.checkDuplicateModuleReferences(statement); + if (!hasErrorResults()) myModuleChecker.checkPackageReference(statement); } } diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ModuleChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ModuleChecker.java index 965f13021ce2..86a057e1ed84 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ModuleChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ModuleChecker.java @@ -282,4 +282,16 @@ final class ModuleChecker { } } } + + void checkPackageReference(@NotNull PsiPackageAccessibilityStatement statement) { + if (statement.getRole() == PsiPackageAccessibilityStatement.Role.OPENS) return; + PsiJavaCodeReferenceElement refElement = statement.getPackageReference(); + if (refElement == null) return; + JavaPsiModuleUtil.PackageReferenceState state = JavaPsiModuleUtil.checkPackageReference(statement); + if (state == JavaPsiModuleUtil.PackageReferenceState.VALID) return; + var kind = state == JavaPsiModuleUtil.PackageReferenceState.PACKAGE_NOT_FOUND + ? JavaErrorKinds.MODULE_REFERENCE_PACKAGE_NOT_FOUND + : JavaErrorKinds.MODULE_REFERENCE_PACKAGE_EMPTY; + myVisitor.report(kind.create(statement)); + } } diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index 047dd3308bd4..5ec8c8bed227 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -1457,6 +1457,14 @@ public final class JavaErrorKinds { public static final Simple MODULE_DUPLICATE_OPENS_TARGET = error(PsiJavaModuleReferenceElement.class, "module.duplicate.opens.target") .withRawDescription(ref -> message("module.duplicate.opens.target", ref.getReferenceText())); + public static final Simple MODULE_REFERENCE_PACKAGE_NOT_FOUND = + error(PsiPackageAccessibilityStatement.class, "module.reference.package.not.found") + .withAnchor(st -> st.getPackageReference()) + .withRawDescription(st -> message("module.reference.package.not.found", st.getPackageName())); + public static final Simple MODULE_REFERENCE_PACKAGE_EMPTY = + error(PsiPackageAccessibilityStatement.class, "module.reference.package.empty") + .withAnchor(st -> st.getPackageReference()) + .withRawDescription(st -> message("module.reference.package.empty", st.getPackageName())); private static @NotNull Simple error( @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) { diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index 5202acd2f18f..640f989428c3 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -218,6 +218,7 @@ inspection.implicit.subclass.make.class.extendable=Make class ''{0}'' {1,choice, inspection.infinite.loop.option=Ignore when placed in Thread.run inspection.java.module.naming.terminal.digits=Module name component ''{0}'' should avoid terminal digits inspection.java.module.naming=Java module name contradicts the convention +inspection.java.module.definition=Java module definition problems inspection.local.can.be.final.display.name=Local variable or parameter can be 'final' inspection.local.can.be.final.option1=Report method parameters inspection.local.can.be.final.option2=Report catch parameters @@ -611,3 +612,5 @@ safe.varargs.not.suppress.potentially.unsafe.operations=@SafeVarargs do not supp safe.varargs.on.reifiable.type=@SafeVarargs is not applicable to reifiable types inspection.unreachable.catch.name=Unreachable catch section inspection.unreachable.catch.message=Unreachable section: {1, choice, 0#exception|2#exceptions} ''{0}'' {1, choice, 0#has|2#have} already been caught +module.service.unused=Service interface provided but not exported or used +module.ambiguous=Ambiguous module reference: {0} diff --git a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml index a1e7ffb19bbc..006a49b4d5de 100644 --- a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml @@ -89,6 +89,10 @@ groupPath="Java" groupBundle="messages.InspectionsBundle" groupKey="group.names.naming.conventions" bundle="messages.JavaAnalysisBundle" key="inspection.java.module.naming" implementationClass="com.intellij.codeInspection.java19modules.JavaModuleNamingInspection"/> + createClassInPackage = error -> { + String packageName = error.psi().getPackageName(); + Module module = ModuleUtilCore.findModuleForFile(error.psi().getContainingFile()); + return module == null ? null : myFactory.createCreateClassInPackageInModuleFix(module, packageName); + }; + fix(MODULE_REFERENCE_PACKAGE_NOT_FOUND, createClassInPackage); + fix(MODULE_REFERENCE_PACKAGE_EMPTY, createClassInPackage); } private void createStatementFixes() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java index 9650f7c2df1e..21ffc3d84477 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java @@ -5,74 +5,19 @@ import com.intellij.codeInsight.JavaModuleSystemEx; import com.intellij.codeInsight.daemon.JavaErrorBundle; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; -import com.intellij.codeInsight.daemon.impl.quickfix.AddExportsDirectiveFix; -import com.intellij.codeInsight.daemon.impl.quickfix.AddUsesDirectiveFix; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.QuickFixFactory; -import com.intellij.modcommand.ModCommandAction; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtilCore; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; -import com.intellij.psi.PsiPackageAccessibilityStatement.Role; import com.intellij.psi.impl.IncompleteModelUtil; -import com.intellij.psi.util.JavaMultiReleaseUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.JBIterable; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; - -import static java.util.Objects.requireNonNullElse; // generates HighlightInfoType.ERROR-like HighlightInfos for modularity-related (Jigsaw) problems final class ModuleHighlightUtil { - static void checkUnusedServices(@NotNull PsiJavaModule module, @NotNull PsiFile file, @NotNull Consumer errorSink) { - Module host = ModuleUtilCore.findModuleForFile(file); - if (host == null) { - return; - } - List provides = JBIterable.from(module.getProvides()).toList(); - if (!provides.isEmpty()) { - Set exports = JBIterable.from(module.getExports()).map(PsiPackageAccessibilityStatement::getPackageName).filter(Objects::nonNull).toSet(); - Set uses = JBIterable.from(module.getUses()).map(st -> qName(st.getClassReference())).filter(Objects::nonNull).toSet(); - for (PsiProvidesStatement statement : provides) { - PsiJavaCodeReferenceElement ref = statement.getInterfaceReference(); - if (ref != null) { - PsiElement target = ref.resolve(); - if (target instanceof PsiClass && ModuleUtilCore.findModuleForFile(target.getContainingFile()) == host) { - String className = qName(ref); - String packageName = StringUtil.getPackageName(className); - if (!exports.contains(packageName) && !uses.contains(className)) { - String message = JavaErrorBundle.message("module.service.unused"); - HighlightInfo.Builder info = - HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range( - requireNonNullElse(ref.getReferenceNameElement(), ref)).descriptionAndTooltip(message); - ModCommandAction action1 = new AddExportsDirectiveFix(module, packageName, ""); - info.registerFix(action1, null, null, null, null); - ModCommandAction action = new AddUsesDirectiveFix(module, className); - info.registerFix(action, null, null, null, null); - errorSink.accept(info); - } - } - } - } - } - } - - private static String qName(PsiJavaCodeReferenceElement ref) { - return ref != null ? ref.getQualifiedName() : null; - } - static HighlightInfo.Builder checkModuleReference(@NotNull PsiImportModuleStatement statement) { PsiJavaModuleReferenceElement refElement = statement.getModuleReference(); if (refElement == null) return null; @@ -93,29 +38,6 @@ final class ModuleHighlightUtil { return null; } - static HighlightInfo.Builder 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) { - 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())); - } - } - } - - return null; - } - private static @NotNull HighlightInfo.Builder getUnresolvedJavaModuleReason(@NotNull PsiElement parent, @NotNull PsiJavaModuleReferenceElement refElement) { PsiJavaModuleReference ref = refElement.getReference(); assert ref != null : refElement.getParent(); @@ -145,55 +67,4 @@ final class ModuleHighlightUtil { .descriptionAndTooltip(JavaErrorBundle.message("module.ambiguous", refElement.getReferenceText())); } } - - static HighlightInfo.Builder checkPackageReference(@NotNull PsiPackageAccessibilityStatement statement, @NotNull PsiFile file) { - PsiJavaCodeReferenceElement refElement = statement.getPackageReference(); - if (refElement != null) { - Module module = ModuleUtilCore.findModuleForFile(file); - if (module != null) { - PsiElement target = refElement.resolve(); - PsiDirectory[] directories = PsiDirectory.EMPTY_ARRAY; - if (target instanceof PsiPackage psiPackage) { - boolean inTests = ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(file.getVirtualFile()); - directories = psiPackage.getDirectories(module.getModuleScope(inTests)); - Module mainMultiReleaseModule = JavaMultiReleaseUtil.getMainMultiReleaseModule(module); - if (mainMultiReleaseModule != null) { - directories = ArrayUtil.mergeArrays(directories, psiPackage.getDirectories(mainMultiReleaseModule.getModuleScope(inTests))); - } - } - String packageName = statement.getPackageName(); - boolean opens = statement.getRole() == Role.OPENS; - HighlightInfoType type = opens ? HighlightInfoType.WARNING : HighlightInfoType.ERROR; - if (directories.length == 0) { - String message = JavaErrorBundle.message("package.not.found", packageName); - HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message); - IntentionAction action = QuickFixFactory.getInstance().createCreateClassInPackageInModuleFix(module, packageName); - if (action != null) { - info.registerFix(action, null, null, null, null); - } - return info; - } - if (packageName != null && isPackageEmpty(directories, packageName, opens)) { - String message = JavaErrorBundle.message("package.is.empty", packageName); - HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message); - IntentionAction action = QuickFixFactory.getInstance().createCreateClassInPackageInModuleFix(module, packageName); - if (action != null) { - info.registerFix(action, null, null, null, null); - } - return info; - } - } - } - - return null; - } - - private static boolean isPackageEmpty(PsiDirectory @NotNull [] directories, @NotNull String packageName, boolean anyFile) { - if (anyFile) { - return !ContainerUtil.exists(directories, dir -> dir.getFiles().length > 0); - } - else { - return PsiUtil.isPackageEmpty(directories, packageName); - } - } } \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/JavaModuleDefinitionInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/JavaModuleDefinitionInspection.java new file mode 100644 index 000000000000..5f95b2296707 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java19modules/JavaModuleDefinitionInspection.java @@ -0,0 +1,113 @@ +// 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.java19modules; + +import com.intellij.codeInsight.daemon.impl.quickfix.AddExportsDirectiveFix; +import com.intellij.codeInsight.daemon.impl.quickfix.AddUsesDirectiveFix; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool; +import com.intellij.codeInspection.LocalQuickFixBackedByIntentionAction; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.java.analysis.JavaAnalysisBundle; +import com.intellij.java.codeserver.core.JavaPsiModuleUtil; +import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.JBIterable; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static java.util.Objects.requireNonNullElse; + +public final class JavaModuleDefinitionInspection extends AbstractBaseJavaLocalInspectionTool { + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return !PsiUtil.isModuleFile(holder.getFile()) ? PsiElementVisitor.EMPTY_VISITOR : new JavaElementVisitor() { + @Override + public void visitModule(@NotNull PsiJavaModule module) { + checkUnusedServices(module); + } + + @Override + public void visitRequiresStatement(@NotNull PsiRequiresStatement statement) { + checkModuleReference(statement); + } + + @Override + public void visitPackageAccessibilityStatement(@NotNull PsiPackageAccessibilityStatement statement) { + if (statement.getRole() != PsiPackageAccessibilityStatement.Role.OPENS) return; + PsiJavaCodeReferenceElement refElement = statement.getPackageReference(); + if (refElement == null) return; + JavaPsiModuleUtil.PackageReferenceState state = JavaPsiModuleUtil.checkPackageReference(statement); + if (state == JavaPsiModuleUtil.PackageReferenceState.VALID) return; + + var kind = state == JavaPsiModuleUtil.PackageReferenceState.PACKAGE_NOT_FOUND + ? JavaErrorKinds.MODULE_REFERENCE_PACKAGE_NOT_FOUND + : JavaErrorKinds.MODULE_REFERENCE_PACKAGE_EMPTY; + String message = kind.description(statement, null).toString(); + String packageName = statement.getPackageName(); + Module module = ModuleUtilCore.findModuleForFile(holder.getFile()); + IntentionAction action = + module == null ? null : QuickFixFactory.getInstance().createCreateClassInPackageInModuleFix(module, packageName); + holder.problem(refElement, message) + .maybeFix(action == null ? null : new LocalQuickFixBackedByIntentionAction(action)) + .register(); + } + + void checkModuleReference(@NotNull PsiRequiresStatement statement) { + PsiJavaModuleReferenceElement refElement = statement.getReferenceElement(); + if (refElement != null) { + PsiJavaModuleReference ref = refElement.getReference(); + if (ref != null) { + ResolveResult[] results = ref.multiResolve(true); + if (results.length > 1 && ref.resolve() == null) { + holder.registerProblem(refElement, JavaAnalysisBundle.message("module.ambiguous", refElement.getReferenceText())); + } + } + } + } + + private void checkUnusedServices(@NotNull PsiJavaModule module) { + Module host = ModuleUtilCore.findModuleForFile(holder.getFile()); + if (host == null) { + return; + } + List provides = JBIterable.from(module.getProvides()).toList(); + if (!provides.isEmpty()) { + Set + exports = + JBIterable.from(module.getExports()).map(PsiPackageAccessibilityStatement::getPackageName).filter(Objects::nonNull).toSet(); + Set uses = JBIterable.from(module.getUses()).map(st -> qName(st.getClassReference())).filter(Objects::nonNull).toSet(); + for (PsiProvidesStatement statement : provides) { + + PsiJavaCodeReferenceElement ref = statement.getInterfaceReference(); + if (ref != null) { + PsiElement target = ref.resolve(); + if (target instanceof PsiClass && ModuleUtilCore.findModuleForFile(target.getContainingFile()) == host) { + String className = qName(ref); + String packageName = StringUtil.getPackageName(className); + if (!exports.contains(packageName) && !uses.contains(className)) { + holder.problem(requireNonNullElse(ref.getReferenceNameElement(), ref), + JavaAnalysisBundle.message("module.service.unused")) + .fix(new AddExportsDirectiveFix(module, packageName, "")) + .fix(new AddUsesDirectiveFix(module, className)) + .register(); + } + } + } + } + } + } + }; + } + + private static String qName(PsiJavaCodeReferenceElement ref) { + return ref != null ? ref.getQualifiedName() : null; + } +} \ No newline at end of file diff --git a/java/java-impl/resources/inspectionDescriptions/JavaModuleDefinition.html b/java/java-impl/resources/inspectionDescriptions/JavaModuleDefinition.html new file mode 100644 index 000000000000..594c6030bd0f --- /dev/null +++ b/java/java-impl/resources/inspectionDescriptions/JavaModuleDefinition.html @@ -0,0 +1,13 @@ + + +Reports miscellaneous problems with the module-info.java file. For example, it reports a service which is provided but not exported or used. +

Example:

+

+module myModule {
+  // Service is provided but its containing package is not exported
+  provides com.example.MyService with com.example.MyServiceImpl;
+}
+
+ + + diff --git a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties index 8b80e368d376..8718c9397108 100644 --- a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties @@ -193,7 +193,6 @@ module.ambiguous=Ambiguous module reference: {0} module.not.on.path=Module is not in dependencies: {0} package.not.found=Package not found: {0} package.is.empty=Package is empty: {0} -module.service.unused=Service interface provided but not exported or used module.access.to.unnamed=Package ''{0}'' is declared in the unnamed module, but module ''{1}'' does not read it module.access.from.named=Package ''{0}'' is declared in module ''{1}'', which does not export it to module ''{2}'' module.access.from.unnamed=Package ''{0}'' is declared in module ''{1}'', which does not export it to the unnamed module diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/CreateClassInPackageInModuleTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/CreateClassInPackageInModuleTest.kt index 79e0f3428b12..d94bd54aa4d2 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/CreateClassInPackageInModuleTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/CreateClassInPackageInModuleTest.kt @@ -3,11 +3,17 @@ package com.intellij.codeInsight.daemon.impl.quickfix import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil import com.intellij.codeInsight.intention.impl.CreateClassInPackageInModuleFix +import com.intellij.codeInspection.java19modules.JavaModuleDefinitionInspection import com.intellij.psi.PsiJavaFile import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase class CreateClassInPackageInModuleTest : LightJavaCodeInsightFixtureTestCase() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(JavaModuleDefinitionInspection()) + } + fun testExportsMissingDir(): Unit = doTestMissingDir("exports") fun testOpensMissingDir(): Unit = doTestMissingDir("opens") diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt index 298c23e85811..7921928bb9ad 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt @@ -7,6 +7,7 @@ import com.intellij.codeInsight.intention.IntentionActionDelegate import com.intellij.codeInspection.IllegalDependencyOnInternalPackageInspection import com.intellij.codeInspection.deprecation.DeprecationInspection import com.intellij.codeInspection.deprecation.MarkedForRemovalInspection +import com.intellij.codeInspection.java19modules.JavaModuleDefinitionInspection import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.* @@ -14,6 +15,7 @@ import com.intellij.java.workspace.entities.JavaModuleSettingsEntity import com.intellij.java.workspace.entities.javaSettings import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runWriteActionAndWait +import com.intellij.openapi.diagnostic.ReportingClassSubstitutor import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.roots.ModuleRootManager @@ -50,6 +52,8 @@ import java.util.jar.JarFile class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { override fun setUp() { super.setUp() + + myFixture.enableInspections(JavaModuleDefinitionInspection()) addFile("module-info.java", "module M2 { }", M2) addFile("module-info.java", "module M3 { }", M3) @@ -289,7 +293,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { } fun testWeakModule() { - highlight("""open module M { opens pkg.missing; }""") + highlight("""open module M { opens pkg.missing; }""") } fun testUses() { @@ -327,6 +331,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { import pkg.main.Impl6; module M { requires M2; + exports pkg.main; provides pkg.main.C with pkg.main.NoImpl; provides pkg.main.C with pkg.main.Impl1; provides pkg.main.C with pkg.main.Impl2; @@ -973,9 +978,10 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { myFixture.configureFromExistingVirtualFile(addFile(path, text)) val availableIntentions = myFixture.availableIntentions val available = availableIntentions - .map { (it.asModCommandAction() ?: IntentionActionDelegate.unwrap(it))::class.java } + .map { ReportingClassSubstitutor.getClassToReport(it) } .filter { it.name.startsWith("com.intellij.codeInsight.") && !(it.name.startsWith("com.intellij.codeInsight.intention.impl.") && it.name.endsWith("Action")) + && !it.name.endsWith("DisableHighlightingIntentionAction") && !it.name.endsWith("DeclarativeHintsTogglingIntention")} .map { it.simpleName } assertThat(available).describedAs(availableIntentions.toString()).containsExactlyInAnyOrder(*fixes)