mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 21:41:24 +07:00
[java, complete, import-module] Check access module names for Module Import Declarations DEA-356710
GitOrigin-RevId: ef96cf46f062068539cc417a3e130172fd4b6132
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fe56e465d6
commit
85c104a858
@@ -230,6 +230,10 @@ public final class JavaModuleGraphUtil {
|
||||
return getRequiresGraph(source).reads(source, destination);
|
||||
}
|
||||
|
||||
public static boolean reads(@NotNull PsiJavaModule source, @NotNull String destination) {
|
||||
return getRequiresGraph(source).reads(source, source, destination);
|
||||
}
|
||||
|
||||
public static @NotNull Set<PsiJavaModule> getAllDependencies(PsiJavaModule source) {
|
||||
return getRequiresGraph(source).getAllDependencies(source);
|
||||
}
|
||||
@@ -505,6 +509,26 @@ public final class JavaModuleGraphUtil {
|
||||
myTransitiveEdges = transitiveEdges;
|
||||
}
|
||||
|
||||
public boolean reads(@NotNull PsiJavaModule source, @NotNull String destination) {
|
||||
return reads(source, source, destination);
|
||||
}
|
||||
|
||||
private boolean reads(@NotNull PsiJavaModule top, @NotNull PsiJavaModule source, @NotNull String destination) {
|
||||
Collection<PsiJavaModule> nodes = myGraph.getNodes();
|
||||
if (ContainerUtil.exists(nodes, m -> m.getName().equals(destination)) && nodes.contains(source)) {
|
||||
Iterator<PsiJavaModule> directReaders = myGraph.getIn(source);
|
||||
while (directReaders.hasNext()) {
|
||||
PsiJavaModule next = directReaders.next();
|
||||
if (top.equals(source)) {
|
||||
if (next.getName().equals(destination) || reads(top, next, destination)) return true;
|
||||
} else if(myTransitiveEdges.contains(key(next, source))) {
|
||||
if (next.getName().equals(destination) || reads(top, next, destination)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean reads(PsiJavaModule source, PsiJavaModule destination) {
|
||||
Collection<PsiJavaModule> nodes = myGraph.getNodes();
|
||||
if (nodes.contains(destination) && nodes.contains(source)) {
|
||||
|
||||
@@ -66,6 +66,7 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.JavaDeprecationUtils;
|
||||
@@ -1387,8 +1388,9 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
|
||||
private static void addModuleReferences(PsiElement moduleRef, PsiElement position, PsiFile originalFile, CompletionResultSet result) {
|
||||
PsiElement statement = moduleRef.getParent();
|
||||
boolean withAutoModules;
|
||||
if ((withAutoModules = statement instanceof PsiRequiresStatement || statement instanceof PsiImportModuleStatement) || statement instanceof PsiPackageAccessibilityStatement) {
|
||||
boolean withAutoModules, checkAccess;
|
||||
if ((withAutoModules = (checkAccess = statement instanceof PsiImportModuleStatement) || statement instanceof PsiRequiresStatement) ||
|
||||
statement instanceof PsiPackageAccessibilityStatement) {
|
||||
PsiElement parent = statement.getParent();
|
||||
if (parent != null) {
|
||||
Project project = moduleRef.getProject();
|
||||
@@ -1415,9 +1417,12 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
|
||||
JavaModuleNameIndex index = JavaModuleNameIndex.getInstance();
|
||||
GlobalSearchScope scope = ProjectScope.getAllScope(project);
|
||||
PsiJavaModule currentModule = JavaModuleGraphUtil.findDescriptorByElement(originalFile);
|
||||
for (String name : index.getAllKeys(project)) {
|
||||
if (!index.getModules(name, project, scope).isEmpty() && filter.add(name)) {
|
||||
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 (withAutoModules) lookup = TailTypeDecorator.withTail(lookup, TailTypes.semicolonType());
|
||||
result.addElement(lookup);
|
||||
}
|
||||
@@ -1438,7 +1443,15 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
shadowedNames.add(LightJavaModule.moduleName(jarRoot.getNameWithoutExtension()));
|
||||
}
|
||||
}
|
||||
addAutoModuleReference(name, parent, filter, result);
|
||||
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);
|
||||
return lookupElement;
|
||||
});
|
||||
if (lookup != null) {
|
||||
result.addElement(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualFile[] roots = ModuleRootManager.getInstance(module).orderEntries().withoutSdk().librariesOnly().getClassesRoots();
|
||||
@@ -1447,8 +1460,17 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
if (shadowedNames.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
if (!JavaAutoModuleNameIndex.getFilesByKey(name, scope).isEmpty()) {
|
||||
addAutoModuleReference(name, parent, filter, result);
|
||||
Collection<VirtualFile> files = JavaAutoModuleNameIndex.getFilesByKey(name, scope);
|
||||
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);
|
||||
return lookupElement;
|
||||
});
|
||||
if (lookup != null) {
|
||||
result.addElement(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1457,6 +1479,81 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the given {@link LookupElement} by modifying its appearance.
|
||||
* The text of the element is displayed in red color.
|
||||
*
|
||||
* @param lookup the {@link LookupElement} to be highlighted
|
||||
* @return a new {@link LookupElement} with adjusted proximity and custom rendering
|
||||
*/
|
||||
@NotNull
|
||||
private static LookupElement highlightLookup(@NotNull LookupElement lookup) {
|
||||
return PrioritizedLookupElement.withExplicitProximity(LookupElementDecorator.withRenderer(lookup, new LookupElementRenderer<>() {
|
||||
@Override
|
||||
public void renderElement(LookupElementDecorator<LookupElement> element, LookupElementPresentation presentation) {
|
||||
element.getDelegate().renderElement(presentation);
|
||||
presentation.setItemTextForeground(JBColor.RED);
|
||||
}
|
||||
}), -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.
|
||||
@@ -1494,13 +1591,14 @@ public final class JavaCompletionContributor extends CompletionContributor imple
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addAutoModuleReference(String name, PsiElement parent, Set<? super String> filter, CompletionResultSet result) {
|
||||
@Nullable
|
||||
private static LookupElement getAutoModuleReference(@NotNull String name, @NotNull PsiElement parent,
|
||||
@NotNull Set<? super String> filter, @NotNull Function<LookupElement, LookupElement> wrapper) {
|
||||
if (PsiNameHelper.isValidModuleName(name, parent) && filter.add(name)) {
|
||||
LookupElement lookup = LookupElementBuilder.create(name).withIcon(AllIcons.FileTypes.Archive);
|
||||
lookup = TailTypeDecorator.withTail(lookup, TailTypes.semicolonType());
|
||||
lookup = PrioritizedLookupElement.withPriority(lookup, -1);
|
||||
result.addElement(lookup);
|
||||
return wrapper.apply(TailTypeDecorator.withTail(lookup, TailTypes.semicolonType()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static class IndentingDecorator extends LookupElementDecorator<LookupElement> {
|
||||
|
||||
@@ -3,10 +3,13 @@ package com.intellij.java.codeInsight.completion
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionType
|
||||
import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
|
||||
import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M2
|
||||
import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M4
|
||||
import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.*
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.testFramework.NeedsIndex
|
||||
import com.intellij.ui.JBColor
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import java.awt.Color
|
||||
import java.util.jar.JarFile
|
||||
|
||||
class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
@@ -249,6 +252,74 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
myFixture.assertPreferredCompletionItems(0, "MyClassC", "MyClassB", "MyClassA")
|
||||
}
|
||||
|
||||
fun testReadableCompletion1() {
|
||||
addFile("module-info.java", "module current.module.name { requires first.module.name; }")
|
||||
addFile("module-info.java", "module first.module.name { }", M2)
|
||||
addFile("module-info.java", "module second.module.name { }", M4)
|
||||
|
||||
fileComplete("MyClass.java", """
|
||||
import module module<caret>
|
||||
public class MyClass { }
|
||||
""".trimIndent(), mapOf("current.module.name" to JBColor.foreground(),
|
||||
"first.module.name" to JBColor.foreground(),
|
||||
"second.module.name" to JBColor.RED))
|
||||
}
|
||||
|
||||
fun testReadableCompletion2() {
|
||||
addFile("module-info.java", "module current.module.name { requires first.module.name; }")
|
||||
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: first.module.name\n", M2)
|
||||
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: second.module.name\n", M4)
|
||||
|
||||
fileComplete("MyClass.java", """
|
||||
import module module<caret>
|
||||
public class MyClass { }
|
||||
""".trimIndent(), mapOf("current.module.name" to JBColor.foreground(),
|
||||
"first.module.name" to JBColor.foreground(),
|
||||
"second.module.name" to JBColor.RED))
|
||||
}
|
||||
|
||||
fun testReadableCompletion3() {
|
||||
addFile("module-info.java", "module first.module.name { }", M2)
|
||||
addFile("module-info.java", "module second.module.name { }", M3)
|
||||
|
||||
fileComplete("MyClass.java", """
|
||||
import module module<caret>
|
||||
public class MyClass { }
|
||||
""".trimIndent(), mapOf("first.module.name" to JBColor.foreground(),
|
||||
"second.module.name" to JBColor.RED))
|
||||
}
|
||||
|
||||
fun testReadableCompletion4() {
|
||||
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: first.module.name\n", M2)
|
||||
addFile(JarFile.MANIFEST_NAME, "Manifest-Version: 1.0\nAutomatic-Module-Name: second.module.name\n", M3)
|
||||
|
||||
fileComplete("MyClass.java", """
|
||||
import module module<caret>
|
||||
public class MyClass { }
|
||||
""".trimIndent(), mapOf("first.module.name" to JBColor.foreground(),
|
||||
"second.module.name" to JBColor.RED))
|
||||
}
|
||||
|
||||
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)
|
||||
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)!!)
|
||||
ModuleRootModificationUtil.addDependency(ModuleManager.getInstance(project).findModuleByName(M4.moduleName)!!,
|
||||
ModuleManager.getInstance(project).findModuleByName(M5.moduleName)!!)
|
||||
|
||||
fileComplete("MyClass.java", """
|
||||
import module module<caret>
|
||||
public class MyClass { }
|
||||
""".trimIndent(), mapOf("current.module.name" to JBColor.foreground(),
|
||||
"first.module.name" to JBColor.foreground(),
|
||||
"second.module.name" to JBColor.foreground(),
|
||||
"third.module.name" to JBColor.foreground()))
|
||||
}
|
||||
|
||||
|
||||
//<editor-fold desc="Helpers.">
|
||||
private fun complete(text: String, expected: String) = fileComplete("module-info.java", text, expected)
|
||||
private fun fileComplete(fileName: String, text: String, expected: String) {
|
||||
@@ -257,12 +328,38 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
myFixture.checkResult(expected)
|
||||
}
|
||||
|
||||
private fun fileComplete(fileName: String, text: String, expected: Map<String, Color>) {
|
||||
myFixture.configureByText(fileName, text)
|
||||
myFixture.completeBasic()
|
||||
|
||||
myFixture.lookup
|
||||
|
||||
val items = myFixture.lookupElements?.map { lookup ->
|
||||
NormalCompletionTestCase.renderElement(lookup).let { element ->
|
||||
element.itemText to element.itemTextForeground
|
||||
}
|
||||
}?.toMap() ?: emptyMap()
|
||||
|
||||
val error = StringBuilder()
|
||||
expected.forEach { (name, color) ->
|
||||
if (color != items[name]) error.append("""
|
||||
${name}:
|
||||
expected: ${color}
|
||||
actual: ${items[name]}
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
if (error.isNotBlank()) {
|
||||
fail(error.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun variants(text: String, vararg variants: String) = fileVariants("module-info.java", text, *variants)
|
||||
private fun fileVariants(fileName: String, text: String, vararg variants: String) {
|
||||
myFixture.configureByText(fileName, text)
|
||||
myFixture.completeBasic()
|
||||
assertThat(myFixture.lookupElementStrings).containsExactlyInAnyOrder(*variants)
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
}
|
||||
Reference in New Issue
Block a user