[java, highlighting, import-module] Check access module names for Module Import Declarations DEA-356710

GitOrigin-RevId: 819c182488bd0b4aeffb8518373da35aabf311ca
This commit is contained in:
Aleksey Dobrynin
2024-07-31 17:22:42 +02:00
committed by intellij-monorepo-bot
parent 85c104a858
commit 44e14f0283
12 changed files with 278 additions and 78 deletions

View File

@@ -2,10 +2,7 @@
package com.intellij.codeInsight;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.psi.JavaModuleSystem;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -41,4 +38,7 @@ public interface JavaModuleSystemEx extends JavaModuleSystem {
@Nullable
ErrorWithFixes checkAccess(@NotNull String targetPackageName, @Nullable PsiFile targetFile, @NotNull PsiElement place);
@Nullable
ErrorWithFixes checkAccess(@NotNull PsiJavaModule module, @NotNull PsiElement place);
}

View File

@@ -0,0 +1,39 @@
// 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.codeInsight.daemon.impl.analysis;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaModuleSystem;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaModule;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
public final class JavaModuleUtil {
private JavaModuleUtil() { }
/**
* Determines if a specified module is readable from a given context
*
* @param place current module/position
* @param targetModuleFile file from the target module
* @return {@code true} if the target module is readable from the place; {@code false} otherwise.
*/
public static boolean isModuleReadable(@NotNull PsiElement place,
@NotNull VirtualFile targetModuleFile) {
PsiJavaModule targetModule = JavaModuleGraphUtil.findDescriptorByFile(targetModuleFile, place.getProject());
if (targetModule == null) return true;
return isModuleReadable(place, targetModule);
}
/**
* Determines if the specified modules are readable from a given context.
*
* @param place the current position or element from where readability is being checked
* @param targetModule the target module to check readability against
* @return {@code true} if any of the target modules are readable from the current context; {@code false} otherwise
*/
public static boolean isModuleReadable(@NotNull PsiElement place,
@NotNull PsiJavaModule targetModule) {
return ContainerUtil.and(JavaModuleSystem.EP_NAME.getExtensionList(), sys -> sys.isAccessible(targetModule, place));
}
}

View File

@@ -1,6 +1,7 @@
// 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.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.JavaModuleSystemEx;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
@@ -220,8 +221,21 @@ final class ModuleHighlightUtil {
PsiJavaModuleReference ref = refElement.getReference();
assert ref != null : refElement.getParent();
PsiJavaModule target = ref.resolve();
if (target != null) return null;
return getUnresolvedJavaModuleReason(statement, refElement);
if (target == null) return getUnresolvedJavaModuleReason(statement, refElement);
for (JavaModuleSystem moduleSystem : JavaModuleSystem.EP_NAME.getExtensionList()) {
if (!(moduleSystem instanceof JavaModuleSystemEx javaModuleSystemEx)) continue;
JavaModuleSystemEx.ErrorWithFixes fixes = javaModuleSystemEx.checkAccess(target, statement);
if (fixes == null) continue;
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(statement)
.descriptionAndTooltip(JavaErrorBundle.message("module.not.on.path", refElement.getReferenceText()));
fixes.fixes.forEach(fix -> info.registerFix(fix, null, null, null, null));
return info;
}
if(!JavaModuleUtil.isModuleReadable(statement, target)) {
return getUnresolvedJavaModuleReason(statement, refElement);
}
return null;
}
static HighlightInfo.Builder checkModuleReference(@NotNull PsiRequiresStatement statement) {

View File

@@ -7,6 +7,7 @@ import com.intellij.codeInsight.completion.scope.CompletionElement;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.analysis.GenericsHighlightUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleUtil;
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix;
import com.intellij.codeInsight.lookup.*;
@@ -1422,7 +1423,7 @@ public final class JavaCompletionContributor extends CompletionContributor imple
Collection<PsiJavaModule> modules = index.getModules(name, project, scope);
if (!modules.isEmpty() && filter.add(name)) {
LookupElement lookup = LookupElementBuilder.create(name).withIcon(AllIcons.Nodes.JavaModule);
if (checkAccess && !isModuleReadable(parent, currentModule, modules)) lookup = highlightLookup(lookup);
if (checkAccess && !ContainerUtil.and(modules, module -> JavaModuleUtil.isModuleReadable(parent, module))) lookup = highlightLookup(lookup);
if (withAutoModules) lookup = TailTypeDecorator.withTail(lookup, TailTypes.semicolonType());
result.addElement(lookup);
}
@@ -1445,8 +1446,8 @@ public final class JavaCompletionContributor extends CompletionContributor imple
}
LookupElement lookup = getAutoModuleReference(name, parent, filter, lookupElement -> {
if (!checkAccess) return PrioritizedLookupElement.withPriority(lookupElement, -1);
if (currentModule != null && !isModuleReadable(currentModule, name)) return highlightLookup(lookupElement);
if (currentModule == null && !isModuleReadable(parent, manifests)) return highlightLookup(lookupElement);
if (!ContainerUtil.and(manifests, manifest -> JavaModuleUtil.isModuleReadable(parent, manifest)))
return highlightLookup(lookupElement);
return lookupElement;
});
if (lookup != null) {
@@ -1464,8 +1465,8 @@ public final class JavaCompletionContributor extends CompletionContributor imple
if (!files.isEmpty()) {
LookupElement lookup = getAutoModuleReference(name, parent, filter, lookupElement -> {
if (!checkAccess) return PrioritizedLookupElement.withPriority(lookupElement, -1);
if (currentModule != null && !isModuleReadable(currentModule, name)) return highlightLookup(lookupElement);
if (currentModule == null && !isModuleReadable(parent, files)) return highlightLookup(lookupElement);
if (!ContainerUtil.and(files, file -> JavaModuleUtil.isModuleReadable(parent, file)))
return highlightLookup(lookupElement);
return lookupElement;
});
if (lookup != null) {
@@ -1497,63 +1498,6 @@ public final class JavaCompletionContributor extends CompletionContributor imple
}), -1);
}
/**
* Determines if a specified module is readable from a given context
*
* @param place current module/position
* @param targetModuleFiles files from the target module
* @return {@code true} if the target module is readable from the place; {@code false} otherwise.
*/
private static boolean isModuleReadable(@NotNull PsiElement place,
@NotNull Collection<VirtualFile> targetModuleFiles) {
Module currentModule = ModuleUtilCore.findModuleForPsiElement(place);
if (currentModule == null) return false;
GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentModule);
for (VirtualFile targetModuleFile : targetModuleFiles) {
if (scope.contains(targetModuleFile)) return true;
}
return false;
}
/**
* Determines if the specified modules are readable from a given context.
*
* @param place the current position or element from where readability is being checked
* @param currentModule the module from which readability is being checked
* @param targetModules the collection of target modules to check readability against
* @return {@code true} if any of the target modules are readable from the current context; {@code false} otherwise
*/
private static boolean isModuleReadable(@NotNull PsiElement place,
@Nullable PsiJavaModule currentModule,
@NotNull Collection<PsiJavaModule> targetModules) {
if (currentModule != null) {
for (PsiJavaModule targetModule : targetModules) {
if (JavaModuleGraphUtil.reads(currentModule, targetModule)) return true;
}
} else {
for (PsiJavaModule targetModule : targetModules) {
PsiFile file = targetModule.getContainingFile();
if (file == null) continue;
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) continue;
if (isModuleReadable(place, List.of(virtualFile))) return true;
}
}
return false;
}
/**
* Checks if the specified target module is readable from the current module.
*
* @param currentModule the module from which readability is being checked
* @param targetModuleName the name of the target module to check readability against
* @return true if the target module is readable from the current module; false otherwise
*/
private static boolean isModuleReadable(@NotNull PsiJavaModule currentModule,
@NotNull String targetModuleName) {
return JavaModuleGraphUtil.reads(currentModule, targetModuleName);
}
/**
* Searching for a module name in a broken PsiFile when import module declaration typing before the module description.

View File

@@ -259,15 +259,28 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
result.add(0, new AddModuleDependencyFix(reference, currentModule, modules, scope, exported));
}
Set<Library> libraries = targets.stream()
Set<Library> lightLibraries = targets.stream()
.map(e -> e instanceof LightJavaModule ? ((LightJavaModule)e).getRootVirtualFile() : null)
.flatMap(vf -> vf != null ? index.getOrderEntriesForFile(vf).stream() : Stream.empty())
.map(e -> e instanceof LibraryOrderEntry ? ((LibraryOrderEntry)e).getLibrary() : null)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (!libraries.isEmpty()) {
if (!lightLibraries.isEmpty()) {
result.add(new AddLibraryDependencyFix(reference, currentModule,
ContainerUtil.map2Map(libraries, library -> Pair.create(library, "")), scope, exported));
ContainerUtil.map2Map(lightLibraries, library -> Pair.create(library, "")), scope, exported));
}
Set<Library> clsLibraries = targets.stream()
.map(e -> e instanceof PsiCompiledElement ? e.getContainingFile() : null)
.map(f -> f != null ? f.getVirtualFile() : null)
.filter(vf -> vf != null && !index.isInSource(vf))
.flatMap(vf -> index.getOrderEntriesForFile(vf).stream())
.map(e -> e instanceof LibraryOrderEntry ? ((LibraryOrderEntry)e).getLibrary() : null)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (!clsLibraries.isEmpty()) {
result.add(new AddLibraryDependencyFix(reference, currentModule,
ContainerUtil.map2Map(clsLibraries, library -> Pair.create(library, "")), scope, exported));
}
}

View File

@@ -8,6 +8,8 @@ import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil
import com.intellij.codeInsight.daemon.impl.quickfix.AddExportsDirectiveFix
import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiresDirectiveFix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.QuickFixFactory
import com.intellij.codeInspection.util.IntentionName
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessRunner
@@ -65,7 +67,21 @@ internal class JavaPlatformModuleSystem : JavaModuleSystemEx {
return getProblem(targetPackageName, targetFile, place, false) { (current, target) ->
val currentModule = current.module ?: return@getProblem false
val targetModule = target.module ?: return@getProblem false
JavaModuleGraphUtil.reads(currentModule, targetModule)
return@getProblem JavaModuleGraphUtil.reads(currentModule, targetModule)
}
}
override fun isAccessible(targetModule: PsiJavaModule, place: PsiElement): Boolean {
return getProblem(targetModule, place, true) { (current, target) ->
if (current.module == null || target.module == null) return@getProblem false
return@getProblem JavaModuleGraphUtil.reads(current.module, target.module!!)
} == null
}
override fun checkAccess(targetModule: PsiJavaModule, place: PsiElement): ErrorWithFixes? {
return getProblem(targetModule, place, false) { (current, target) ->
if (current.module == null || target.module == null) return@getProblem false
return@getProblem JavaModuleGraphUtil.reads(current.module, target.module!!)
}
}
@@ -76,6 +92,12 @@ internal class JavaPlatformModuleSystem : JavaModuleSystemEx {
return inAddedExports(currentJpsModule, targetModule.name, target.packageName, current.name)
}
private fun getProblem(targetModule: PsiJavaModule, place: PsiElement, quick: Boolean,
isAccessible: (ModuleAccessInfo) -> Boolean): ErrorWithFixes? {
val target = TargetModuleInfo(targetModule, "")
return checkModuleAccess(target, place, quick, isAccessible)
}
private fun getProblem(targetPackageName: String, targetFile: PsiFile?, place: PsiElement, quick: Boolean,
isAccessible: (ModuleAccessInfo) -> Boolean): ErrorWithFixes? {
val originalTargetFile = targetFile?.originalFile
@@ -115,6 +137,74 @@ internal class JavaPlatformModuleSystem : JavaModuleSystemEx {
private val ERR = ErrorWithFixes("-")
private fun checkModuleAccess(
target: TargetModuleInfo, place: PsiElement, quick: Boolean,
isAccessible: (ModuleAccessInfo) -> Boolean,
): ErrorWithFixes? {
val useFile = place.containingFile?.originalFile ?: return null
val useModule = JavaModuleGraphUtil.findDescriptorByElement(useFile).let { if (it is LightJavaModule) null else it }
val current = CurrentModuleInfo(useModule, place)
val targetModule = target.module
if (targetModule != null) {
if (targetModule == current.module) {
return null
}
val currentJpsModule = current.jpsModule
if (current.module == null) {
var origin = targetModule.containingFile?.virtualFile
if (origin == null && targetModule is LightJavaModule) origin = targetModule.rootVirtualFile
if (origin == null || currentJpsModule == null) return null
if (ModuleRootManager.getInstance(currentJpsModule).fileIndex.getOrderEntryForFile(origin) !is JdkOrderEntry) {
val searchScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentJpsModule)
if (searchScope.contains(origin)) return null
return if (quick) ERR
else if (place is PsiJavaModuleReferenceElement) {
val reference: PsiJavaModuleReference = place.reference ?: return null
val registrar: MutableList<IntentionAction> = ArrayList()
QuickFixFactory.getInstance().registerOrderEntryFixes(reference, registrar)
ErrorWithFixes("-", registrar)
}
else null
}
if (targetModule.name.startsWith("java.") &&
targetModule.name != PsiJavaModule.JAVA_BASE &&
!inAddedModules(currentJpsModule, targetModule.name) &&
!accessibleFromLoadedModules(current, target, isAccessible)) {
return if (quick) ERR
else ErrorWithFixes(JavaErrorBundle.message("module.not.in.graph", targetModule.name),
listOf(AddModulesOptionFix(currentJpsModule, targetModule.name).asIntention()))
}
}
if (current.module != null &&
targetModule.name != PsiJavaModule.JAVA_BASE &&
!isAccessible(ModuleAccessInfo(current, target)) &&
!inAddedReads(current.module, targetModule)) {
return when {
quick -> ERR
PsiNameHelper.isValidModuleName(targetModule.name, current.module) -> ErrorWithFixes(JavaErrorBundle.message("module.does.not.read", targetModule.name, current.name),
listOf(AddRequiresDirectiveFix(current.module, targetModule.name).asIntention()))
else -> ErrorWithFixes(JavaErrorBundle.message("module.bad.name", targetModule.name))
}
}
}
else if (current.module != null) {
val autoModule = TargetModuleInfo(detectAutomaticModule(target), target.packageName)
if (autoModule.module != null &&
!isAccessible(ModuleAccessInfo(current, autoModule)) &&
!inAddedReads(current.module, null) &&
!inSameMultiReleaseModule(current, target)) {
return if (quick) ERR else ErrorWithFixes(JavaErrorBundle.message("module.access.to.unnamed", target.packageName, current.name))
}
}
return null
}
private fun checkAccess(target: TargetModuleInfo, place: PsiFileSystemItem, quick: Boolean,
isAccessible: (ModuleAccessInfo) -> Boolean): ErrorWithFixes? {
val useModule = JavaModuleGraphUtil.findDescriptorByElement(place).let { if (it is LightJavaModule) null else it }

View File

@@ -59,4 +59,13 @@ public interface JavaModuleSystem {
* @param place place where accessibility of target is required
*/
boolean isAccessible(@NotNull String targetPackageName, @Nullable PsiFile targetFile, @NotNull PsiElement place);
/**
* Checks accessibility of module in the place
*
* @param targetModule the target java module whose accessibility is being checked
* @param place place where accessibility of target is required
* @return true if the target module is accessible from the specified location, false otherwise
*/
boolean isAccessible(@NotNull PsiJavaModule targetModule, @NotNull PsiElement place);
}

View File

@@ -482,8 +482,11 @@ module.access.to.unnamed=Package ''{0}'' is declared in the unnamed module, but
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
module.access.does.not.read=Package ''{0}'' is declared in module ''{1}'', but module ''{2}'' does not read it
module.does.not.read=Module ''{0}'' fails to read ''{1}''
module.access.not.in.graph=Package ''{0}'' is declared in module ''{1}'', which is not in the module graph
module.not.in.graph=Module ''{0}'' is missing from the module graph
module.access.bad.name=Package ''{0}'' is declared in module with an invalid name (''{1}'')
module.bad.name=Module ''{0}'' has an invalid name
restricted.identifier=''{0}'' is a restricted identifier and cannot be used for type declarations
restricted.identifier.reference=Illegal reference to restricted type ''{0}''

View File

@@ -252,6 +252,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
myFixture.assertPreferredCompletionItems(0, "MyClassC", "MyClassB", "MyClassA")
}
@NeedsIndex.Full
fun testReadableCompletion1() {
addFile("module-info.java", "module current.module.name { requires first.module.name; }")
addFile("module-info.java", "module first.module.name { }", M2)
@@ -265,9 +266,12 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
"second.module.name" to JBColor.RED))
}
@NeedsIndex.Full
fun testReadableCompletion2() {
addFile("module-info.java", "module current.module.name { requires first.module.name; }")
deleteFile("module-info.java", M2)
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: first.module.name\n", M2)
deleteFile("module-info.java", M4)
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: second.module.name\n", M4)
fileComplete("MyClass.java", """
@@ -278,6 +282,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
"second.module.name" to JBColor.RED))
}
@NeedsIndex.Full
fun testReadableCompletion3() {
addFile("module-info.java", "module first.module.name { }", M2)
addFile("module-info.java", "module second.module.name { }", M3)
@@ -289,8 +294,11 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
"second.module.name" to JBColor.RED))
}
@NeedsIndex.Full
fun testReadableCompletion4() {
deleteFile("module-info.java", M2)
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: first.module.name\n", M2)
deleteFile("module-info.java", M3)
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: second.module.name\n", M3)
fileComplete("MyClass.java", """
@@ -300,10 +308,12 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
"second.module.name" to JBColor.RED))
}
@NeedsIndex.Full
fun testReadableCompletionTransitive() {
addFile("module-info.java", "module current.module.name { requires first.module.name; }")
addFile("module-info.java", "module first.module.name { requires transitive second.module.name; }", M2)
addFile("module-info.java", "module second.module.name { requires transitive third.module.name; }", M4)
deleteFile("module-info.java", M5)
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: third.module.name\n", M5)
ModuleRootModificationUtil.addDependency(ModuleManager.getInstance(project).findModuleByName(M2.moduleName)!!,
ModuleManager.getInstance(project).findModuleByName(M4.moduleName)!!)

View File

@@ -14,6 +14,8 @@ import com.intellij.java.workspace.entities.JavaModuleSettingsEntity
import com.intellij.java.workspace.entities.javaSettings
import com.intellij.openapi.application.runWriteActionAndWait
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.util.TextRange
import com.intellij.platform.backend.workspace.WorkspaceModel
@@ -749,6 +751,74 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
""".trimIndent())
}
fun testImportModule() {
addFile("module-info.java", """
module current.module.name {
requires first.module.name;
}""".trimIndent())
addFile("module-info.java", """
module first.module.name {
requires transitive first.auto.module.name;
requires second.auto.module.name;
}""".trimIndent(), M2)
addFile("module-info.java", "module second.module.name {}", M4)
addResourceFile(JarFile.MANIFEST_NAME, "Automatic-Module-Name: first.auto.module.name\n", module = M5)
addResourceFile(JarFile.MANIFEST_NAME, "Automatic-Module-Name: second.auto.module.name\n", module = M6)
ModuleRootModificationUtil.addDependency(ModuleManager.getInstance(project).findModuleByName(M2.moduleName)!!,
ModuleManager.getInstance(project).findModuleByName(M5.moduleName)!!)
ModuleRootModificationUtil.addDependency(ModuleManager.getInstance(project).findModuleByName(M2.moduleName)!!,
ModuleManager.getInstance(project).findModuleByName(M6.moduleName)!!)
IdeaTestUtil.withLevel(module, LanguageLevel.JDK_23_PREVIEW) {
highlight("A.java", """
import module current.module.name;
import module first.module.name;
<error descr="Module is not in dependencies: second.module.name">import module second.module.name;</error>
import module first.auto.module.name;
<error descr="Module is not in dependencies: second.auto.module.name">import module second.auto.module.name;</error>
public class A {
}
""".trimIndent())
}
}
fun testImportModuleWithoutTopModule() {
addFile("module-info.java", """
module first.module.name {
requires transitive first.auto.module.name;
requires second.auto.module.name;
}""".trimIndent(), M2)
addFile("module-info.java", "module second.module.name {}", M4)
addFile("module-info.java", "module third.module.name {}", M3)
addResourceFile(JarFile.MANIFEST_NAME, "Automatic-Module-Name: first.auto.module.name\n", module = M5)
addResourceFile(JarFile.MANIFEST_NAME, "Automatic-Module-Name: second.auto.module.name\n", module = M6)
ModuleRootModificationUtil.addDependency(ModuleManager.getInstance(project).findModuleByName(M2.moduleName)!!,
ModuleManager.getInstance(project).findModuleByName(M5.moduleName)!!)
ModuleRootModificationUtil.addDependency(ModuleManager.getInstance(project).findModuleByName(M2.moduleName)!!,
ModuleManager.getInstance(project).findModuleByName(M6.moduleName)!!)
IdeaTestUtil.withLevel(module, LanguageLevel.JDK_23_PREVIEW) {
highlight("A.java", """
import module <error descr="Module not found: current.module.name">current.module.name</error>;
import module first.module.name;
import module second.module.name;
import module <error descr="Module is not in dependencies: third.module.name">third.module.name</error>;
import module first.auto.module.name;
import module second.auto.module.name;
public class A {
}
""".trimIndent())
}
}
fun testAutomaticModuleFromManifestHighlighting() {
addResourceFile(JarFile.MANIFEST_NAME, "Automatic-Module-Name: m6.bar\n", module = M6)
addFile("p/B.java", "package p; public class B {}", M7)

View File

@@ -15,6 +15,8 @@ import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
import org.intellij.lang.annotations.Language
abstract class LightJava9ModulesCodeInsightFixtureTestCase : LightJavaCodeInsightFixtureTestCase() {
private val virtualFiles = HashMap<VirtualFileKey, VirtualFile>()
override fun getProjectDescriptor(): LightProjectDescriptor = MultiModuleJava9ProjectDescriptor
override fun tearDown() {
@@ -30,15 +32,19 @@ abstract class LightJava9ModulesCodeInsightFixtureTestCase : LightJavaCodeInsigh
}
protected fun addFile(path: String, text: String, module: ModuleDescriptor = MAIN): VirtualFile =
VfsTestUtil.createFile(module.sourceRoot()!!, path, text)
VfsTestUtil.createFile(module.sourceRoot()!!, path, text).also { file -> virtualFiles[VirtualFileKey(path, module.sourceRoot()!!)] = file }
protected fun deleteFile(path: String, module: ModuleDescriptor = MAIN) {
virtualFiles[VirtualFileKey(path, module.sourceRoot()!!)]?.let { file -> VfsTestUtil.deleteFile(file) }
}
@JvmOverloads
protected fun addTestFile(path: String, text: String, module: ModuleDescriptor = MAIN): VirtualFile =
VfsTestUtil.createFile(module.testRoot()!!, path, text)
VfsTestUtil.createFile(module.testRoot()!!, path, text).also { file -> virtualFiles[VirtualFileKey(path, module.testRoot()!!)] = file }
@Suppress("SameParameterValue")
protected fun addResourceFile(path: String, text: String, module: ModuleDescriptor = MAIN): VirtualFile =
VfsTestUtil.createFile(module.resourceRoot()!!, path, text)
VfsTestUtil.createFile(module.resourceRoot()!!, path, text).also { file -> virtualFiles[VirtualFileKey(path, module.resourceRoot()!!)] = file }
/**
* @param classNames is like <code>arrayOf("foo.api.Api", "foo.impl.Impl")</code>; the file's directory path is created based on FQN
@@ -63,4 +69,6 @@ abstract class LightJava9ModulesCodeInsightFixtureTestCase : LightJavaCodeInsigh
InspectionTestUtil.runTool(toolWrapper, scope, globalContext)
InspectionTestUtil.compareToolResults(globalContext, toolWrapper, true, testDirPath)
}
private data class VirtualFileKey(val path: String, val root: VirtualFile)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.testFramework.fixtures
import com.intellij.openapi.application.ex.PathManagerEx
@@ -32,7 +32,7 @@ object MultiModuleJava9ProjectDescriptor : DefaultLightProjectDescriptor() {
M2("light_idea_test_m2", sourceRootName = "src_m2"),
M3("light_idea_test_m3", sourceRootName = "src_m3"),
M4("light_idea_test_m4", sourceRootName = "src_m4"),
M5("light_idea_test_m5", sourceRootName = "src_m5"),
M5("light_idea_test_m5", sourceRootName = "src_m5", resourceRootName = "res_m5"),
M6("light_idea_test_m6", sourceRootName = "src_m6", resourceRootName = "res_m6"),
M7("light_idea_test_m7", sourceRootName = "src_m7"),
M8("light_idea_test_m8", sourceRootName = "src_m8"),
@@ -141,7 +141,7 @@ object MultiModuleJava9ProjectDescriptor : DefaultLightProjectDescriptor() {
}
fun cleanupSourceRoots() = runWriteAction {
ModuleDescriptor.values().asSequence()
ModuleDescriptor.entries.asSequence()
.flatMap { sequenceOf(if (it !== ModuleDescriptor.MAIN) it.sourceRoot() else null, it.testRoot(), it.resourceRoot()) }
.filterNotNull()
.flatMap { it.children.asSequence() }