[java, complete, import-module] enhance code completion ordering: support transitive dependencies and implicit imports

GitOrigin-RevId: 737e01da30f0619ecae83693062e8b22adb5f6f3
This commit is contained in:
Aleksey Dobrynin
2024-08-02 16:06:21 +00:00
committed by intellij-monorepo-bot
parent 24ca282a62
commit ab45ebd22f
3 changed files with 90 additions and 47 deletions
@@ -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<PsiJavaModule> getAllDependencies(PsiJavaModule source) {
return getRequiresGraph(source).getAllDependencies(source, false);
}
public static @NotNull Set<PsiJavaModule> getAllDependencies(PsiJavaModule source) {
return getRequiresGraph(source).getAllDependencies(source);
public static @NotNull Set<PsiJavaModule> getAllTransitiveDependencies(PsiJavaModule source) {
return getRequiresGraph(source).getAllDependencies(source, true);
}
public static @Nullable Trinity<String, PsiJavaModule, PsiJavaModule> 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<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)) {
@@ -595,18 +575,18 @@ public final class JavaModuleGraphUtil {
return module.getName() + '/' + exporter.getName();
}
public @NotNull Set<PsiJavaModule> getAllDependencies(@NotNull PsiJavaModule module) {
public @NotNull Set<PsiJavaModule> getAllDependencies(@NotNull PsiJavaModule module, boolean transitive) {
Set<PsiJavaModule> requires = new HashSet<>();
collectDependencies(module, requires);
collectDependencies(module, requires, transitive);
return requires;
}
private void collectDependencies(@NotNull PsiJavaModule module, @NotNull Set<PsiJavaModule> dependencies) {
private void collectDependencies(@NotNull PsiJavaModule module, @NotNull Set<PsiJavaModule> dependencies, boolean transitive) {
for (Iterator<PsiJavaModule> 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);
}
}
}
@@ -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<PsiPackage, ProximityLocation> PLACE_PACKAGE = NullableLazyKey.create("placePackage", location -> {
PsiElement position = location.getPosition();
return position == null ? null : getContextPackage(position);
});
private static final NullableLazyKey<List<String>, 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<List<PsiJavaModule>, 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<String> importedModuleNames = new ArrayList<>();
for (PsiImportModuleStatement statement : importList.getImportModuleStatements()) {
ContainerUtil.addIfNotNull(importedModuleNames, statement.getReferenceName());
}
return importedModuleNames;
});
BiConsumer<List<PsiJavaModule>, PsiJavaModule> append = (list, module) -> {
if (module != null) {
list.add(module);
list.addAll(JavaModuleGraphUtil.getAllTransitiveDependencies(module));
}
};
List<PsiJavaModule> 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<List<String>, 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<String> 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<PsiJavaModule> 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);
@@ -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<caret>
}
}
""".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; }")