From ab45ebd22fcea0aef81f3f22c7eeaa481a4c2136 Mon Sep 17 00:00:00 2001 From: Aleksey Dobrynin Date: Fri, 2 Aug 2024 16:26:45 +0200 Subject: [PATCH] [java, complete, import-module] enhance code completion ordering: support transitive dependencies and implicit imports GitOrigin-RevId: 737e01da30f0619ecae83693062e8b22adb5f6f3 --- .../impl/analysis/JavaModuleGraphUtil.java | 38 ++++---------- .../proximity/ExplicitlyImportedWeigher.java | 48 ++++++++++------- .../completion/ModuleCompletionTest.kt | 51 +++++++++++++++++++ 3 files changed, 90 insertions(+), 47 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java index 9e1dc7504eb9..b18747d634bd 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java @@ -231,12 +231,12 @@ 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 getAllDependencies(PsiJavaModule source) { + return getRequiresGraph(source).getAllDependencies(source, false); } - public static @NotNull Set getAllDependencies(PsiJavaModule source) { - return getRequiresGraph(source).getAllDependencies(source); + public static @NotNull Set getAllTransitiveDependencies(PsiJavaModule source) { + return getRequiresGraph(source).getAllDependencies(source, true); } public static @Nullable Trinity findConflict(@NotNull PsiJavaModule module) { @@ -510,26 +510,6 @@ 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 nodes = myGraph.getNodes(); - if (ContainerUtil.exists(nodes, m -> m.getName().equals(destination)) && nodes.contains(source)) { - Iterator 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 nodes = myGraph.getNodes(); if (nodes.contains(destination) && nodes.contains(source)) { @@ -595,18 +575,18 @@ public final class JavaModuleGraphUtil { return module.getName() + '/' + exporter.getName(); } - public @NotNull Set getAllDependencies(@NotNull PsiJavaModule module) { + public @NotNull Set getAllDependencies(@NotNull PsiJavaModule module, boolean transitive) { Set requires = new HashSet<>(); - collectDependencies(module, requires); + collectDependencies(module, requires, transitive); return requires; } - private void collectDependencies(@NotNull PsiJavaModule module, @NotNull Set dependencies) { + private void collectDependencies(@NotNull PsiJavaModule module, @NotNull Set dependencies, boolean transitive) { for (Iterator iterator = myGraph.getIn(module); iterator.hasNext();) { PsiJavaModule dependency = iterator.next(); - if (!dependencies.contains(dependency)) { + if (!dependencies.contains(dependency) && (!transitive || myTransitiveEdges.contains(key(dependency, module)))) { dependencies.add(dependency); - collectDependencies(dependency, dependencies); + collectDependencies(dependency, dependencies, transitive); } } } diff --git a/java/java-impl/src/com/intellij/psi/util/proximity/ExplicitlyImportedWeigher.java b/java/java-impl/src/com/intellij/psi/util/proximity/ExplicitlyImportedWeigher.java index 45888adf5670..de7c419e7220 100644 --- a/java/java-impl/src/com/intellij/psi/util/proximity/ExplicitlyImportedWeigher.java +++ b/java/java-impl/src/com/intellij/psi/util/proximity/ExplicitlyImportedWeigher.java @@ -1,16 +1,14 @@ // 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.psi.util.proximity; +import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.util.NotNullLazyKey; import com.intellij.openapi.util.NullableLazyKey; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; -import com.intellij.psi.util.ProximityLocation; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.PsiUtilCore; +import com.intellij.psi.util.*; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,23 +16,37 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.BiConsumer; public final class ExplicitlyImportedWeigher extends ProximityWeigher { private static final NullableLazyKey PLACE_PACKAGE = NullableLazyKey.create("placePackage", location -> { PsiElement position = location.getPosition(); return position == null ? null : getContextPackage(position); }); - private static final NullableLazyKey, ProximityLocation> PLACE_IMPORTED_MODULE_NAMES = NullableLazyKey.create("importedModuleNames", location -> { - final PsiJavaFile psiJavaFile = PsiTreeUtil.getContextOfType(location.getPosition(), PsiJavaFile.class, false); - final PsiImportList importList = psiJavaFile == null ? null : psiJavaFile.getImportList(); - if (importList == null) return Collections.emptyList(); + private static final NullableLazyKey, ProximityLocation> PLACE_IMPORTED_MODULES = + NullableLazyKey.create("importedModuleNames", location -> { + final PsiJavaFile psiJavaFile = PsiTreeUtil.getContextOfType(location.getPosition(), PsiJavaFile.class, false); + final PsiImportList importList = psiJavaFile == null ? null : psiJavaFile.getImportList(); + if (importList == null) return Collections.emptyList(); - List importedModuleNames = new ArrayList<>(); - for (PsiImportModuleStatement statement : importList.getImportModuleStatements()) { - ContainerUtil.addIfNotNull(importedModuleNames, statement.getReferenceName()); - } - return importedModuleNames; - }); + BiConsumer, PsiJavaModule> append = (list, module) -> { + if (module != null) { + list.add(module); + list.addAll(JavaModuleGraphUtil.getAllTransitiveDependencies(module)); + } + }; + + List importedModules = new ArrayList<>(); + for (PsiImportModuleStatement statement : importList.getImportModuleStatements()) { + append.accept(importedModules, statement.resolveTargetModule()); + } + for (PsiImportStatementBase statement : ImportsUtil.getAllImplicitImports(psiJavaFile)) { + if (statement instanceof PsiImportModuleStatement moduleStatement) { + append.accept(importedModules, moduleStatement.resolveTargetModule()); + } + } + return importedModules; + }); private static final NotNullLazyKey, ProximityLocation> PLACE_IMPORTED_NAMES = NotNullLazyKey.createLazyKey("importedNames", location -> { final PsiJavaFile psiJavaFile = PsiTreeUtil.getContextOfType(location.getPosition(), PsiJavaFile.class, false); @@ -106,10 +118,10 @@ public final class ExplicitlyImportedWeigher extends ProximityWeigher { return ImportWeight.CLASS_ON_DEMAND_NESTED; } - List moduleNames = PLACE_IMPORTED_MODULE_NAMES.getValue(location); - if (moduleNames != null && !moduleNames.isEmpty()) { - PsiJavaModule psiJavaModule = JavaModuleGraphHelper.getInstance().findDescriptorByElement(element); - if (psiJavaModule != null && moduleNames.contains(psiJavaModule.getName())) return ImportWeight.MODULE_IMPORTED; + List importedModules = PLACE_IMPORTED_MODULES.getValue(location); + if (importedModules != null && !importedModules.isEmpty()) { + PsiJavaModule suggestedModule = JavaModuleGraphHelper.getInstance().findDescriptorByElement(element); + if (suggestedModule != null && importedModules.contains(suggestedModule)) return ImportWeight.MODULE_IMPORTED; } final PsiPackage placePackage = PLACE_PACKAGE.getValue(location); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt index e9539ea9f5c9..a29f4dd4f2ad 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt @@ -252,6 +252,57 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() { myFixture.assertPreferredCompletionItems(0, "MyClassC", "MyClassB", "MyClassA") } + @NeedsIndex.Full + fun testTransitiveModuleImportDeclarationsOrder() { + addFile("module-info.java", """ + module first.module.name { + requires transitive second.module.name; + } + """.trimIndent(), M2) + + addFile("module-info.java", """ + module second.module.name { + exports second.module.name; + } + """.trimIndent(), M4) + addFile("MyClassB.java", """ + package second.module.name; + public class MyClassB { } + """.trimIndent(), M4) + + addFile("module-info.java", """ + module third.module.name { + exports third.module.name; + } + """.trimIndent(), M5) + addFile("MyClassC.java", """ + package third.module.name; + public class MyClassC { } + """.trimIndent(), M4) + + + addFile("module-info.java", """ + module current.module.name { + requires first.module.name; + } + """.trimIndent(), M4) + + myFixture.configureByText("Main.java", """ + import module second.module.name; + import current.pkg.name.*; + + public class Main { + public static void main(String[] args) { + MyCla + } + } + """.trimIndent()) + + myFixture.complete(CompletionType.BASIC) + myFixture.getLookup() + myFixture.assertPreferredCompletionItems(0, "MyClassB", "MyClassC") + } + @NeedsIndex.Full fun testReadableCompletion1() { addFile("module-info.java", "module current.module.name { requires first.module.name; }")