mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[java, highlighting, import-module] Check access module names for Module Import Declarations DEA-356710
GitOrigin-RevId: 819c182488bd0b4aeffb8518373da35aabf311ca
This commit is contained in:
committed by
intellij-monorepo-bot
parent
85c104a858
commit
44e14f0283
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}''
|
||||
|
||||
@@ -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)!!)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
|
||||
Reference in New Issue
Block a user