mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java, quickfix, jigsaw] IDEA-380666 add circular dependency handling for Jigsaw module completion
(cherry picked from commit 0beb868ac32c4cabae9392a9c34cb66086935a02) (cherry picked from commit 5a7924c57cb9afd068e60aed6eea4d3b151cf8de) IJ-MR-182986 GitOrigin-RevId: 201885016bf87fce8b6a98413c63d47de5023539
This commit is contained in:
committed by
intellij-monorepo-bot
parent
470e4f879a
commit
547ccf8e3e
@@ -431,7 +431,7 @@ public final class JavaPsiModuleUtil {
|
||||
PsiJavaModuleReference ref = statement.getModuleReference();
|
||||
if (ref != null) {
|
||||
if (JAVA_BASE.equals(ref.getCanonicalText())) explicitJavaBase = true;
|
||||
for (ResolveResult result : ref.multiResolve(false)) {
|
||||
for (ResolveResult result : ref.multiResolve(true)) {
|
||||
PsiJavaModule dependency = (PsiJavaModule)result.getElement();
|
||||
assert dependency != null : result;
|
||||
relations.putValue(module, dependency);
|
||||
@@ -491,6 +491,8 @@ public final class JavaPsiModuleUtil {
|
||||
}
|
||||
|
||||
public boolean reads(PsiJavaModule source, PsiJavaModule destination) {
|
||||
source = getPhysicalModule(source);
|
||||
destination = getPhysicalModule(destination);
|
||||
Collection<PsiJavaModule> nodes = myGraph.getNodes();
|
||||
if (nodes.contains(destination) && nodes.contains(source)) {
|
||||
Iterator<PsiJavaModule> directReaders = myGraph.getOut(destination);
|
||||
@@ -505,6 +507,7 @@ public final class JavaPsiModuleUtil {
|
||||
}
|
||||
|
||||
private @Nullable ModulePackageConflict findConflict(@NotNull PsiJavaModule source) {
|
||||
source = getPhysicalModule(source);
|
||||
Map<String, PsiJavaModule> exports = new HashMap<>();
|
||||
return processExports(source, (pkg, m) -> {
|
||||
PsiJavaModule found = exports.put(pkg, m);
|
||||
@@ -516,10 +519,11 @@ public final class JavaPsiModuleUtil {
|
||||
}
|
||||
|
||||
private @Nullable PsiJavaModule findOrigin(@NotNull PsiJavaModule module, @NotNull String packageName) {
|
||||
return processExports(module, (pkg, m) -> packageName.equals(pkg) ? m : null);
|
||||
return processExports(getPhysicalModule(module), (pkg, m) -> packageName.equals(pkg) ? m : null);
|
||||
}
|
||||
|
||||
private <T> @Nullable T processExports(@NotNull PsiJavaModule start, @NotNull BiFunction<? super String, ? super PsiJavaModule, ? extends T> processor) {
|
||||
start = getPhysicalModule(start);
|
||||
return myGraph.getNodes().contains(start) ? processExports(start.getName(), start, true, new HashSet<>(), processor) : null;
|
||||
}
|
||||
|
||||
@@ -528,6 +532,7 @@ public final class JavaPsiModuleUtil {
|
||||
boolean direct,
|
||||
@NotNull Set<? super PsiJavaModule> visited,
|
||||
@NotNull BiFunction<? super String, ? super PsiJavaModule, ? extends T> processor) {
|
||||
module = getPhysicalModule(module);
|
||||
if (visited.add(module)) {
|
||||
if (!direct) {
|
||||
for (PsiPackageAccessibilityStatement statement : module.getExports()) {
|
||||
@@ -556,11 +561,12 @@ public final class JavaPsiModuleUtil {
|
||||
|
||||
public @NotNull Set<PsiJavaModule> getAllDependencies(@NotNull PsiJavaModule module, boolean transitive) {
|
||||
Set<PsiJavaModule> requires = new HashSet<>();
|
||||
collectDependencies(module, requires, transitive);
|
||||
collectDependencies(getPhysicalModule(module), requires, transitive);
|
||||
return requires;
|
||||
}
|
||||
|
||||
private void collectDependencies(@NotNull PsiJavaModule module, @NotNull Set<PsiJavaModule> dependencies, boolean transitive) {
|
||||
module = getPhysicalModule(module);
|
||||
for (Iterator<PsiJavaModule> iterator = myGraph.getIn(module); iterator.hasNext();) {
|
||||
PsiJavaModule dependency = iterator.next();
|
||||
if (!dependencies.contains(dependency) && (!transitive || myTransitiveEdges.contains(key(dependency, module)))) {
|
||||
@@ -569,6 +575,14 @@ public final class JavaPsiModuleUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull PsiJavaModule getPhysicalModule(@NotNull PsiJavaModule from) {
|
||||
if (from.isPhysical()) return from;
|
||||
if (!(from.getContainingFile() instanceof PsiJavaFile file)) return from;
|
||||
if (!(file.getOriginalFile() instanceof PsiJavaFile origin)) return from;
|
||||
if (origin.getModuleDeclaration() instanceof PsiJavaModule result) return result;
|
||||
return from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
@@ -104,6 +104,9 @@ public final class JavaModuleGraphUtil {
|
||||
if (to.equals(from.getName())) return false;
|
||||
if (!PsiNameHelper.isValidModuleName(to, from)) return false;
|
||||
if (alreadyContainsRequires(from, to)) return false;
|
||||
|
||||
PsiJavaModule toModule = JavaPsiFacade.getInstance(from.getProject()).findModule(to, from.getResolveScope());
|
||||
if (toModule != null && JavaPsiModuleUtil.reads(toModule, from)) return false; // check for circular dependencies
|
||||
PsiUtil.addModuleStatement(from, JavaKeywords.REQUIRES + " " +
|
||||
(isStaticModule(to, scope) ? JavaKeywords.STATIC + " " : "") +
|
||||
(isExported ? JavaKeywords.TRANSITIVE + " " : "") +
|
||||
@@ -133,6 +136,7 @@ public final class JavaModuleGraphUtil {
|
||||
if (!PsiNameHelper.isValidModuleName(to.getName(), to)) return false;
|
||||
if (contains(from.getRequires(), to.getName())) return false;
|
||||
if (JavaPsiModuleUtil.reads(from, to)) return false;
|
||||
if (JavaPsiModuleUtil.reads(to, from)) return false; // check for circular dependencies
|
||||
PsiUtil.addModuleStatement(from, JavaKeywords.REQUIRES + " " +
|
||||
(isStaticModule(to.getName(), scope) ? JavaKeywords.STATIC + " " : "") +
|
||||
(isExported(from, to) ? JavaKeywords.TRANSITIVE + " " : "") +
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/A/A.iml" filepath="$PROJECT_DIR$/A/A.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/B/B.iml" filepath="$PROJECT_DIR$/B/B.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
module module.a {
|
||||
requires module.main;
|
||||
exports org.jetbrains.a;
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package org.jetbrains.a;
|
||||
|
||||
public class MyAClass {
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module module.b {
|
||||
exports org.jetbrains.b;
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package org.jetbrains.b;
|
||||
|
||||
public class MyBClass {
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="A" />
|
||||
<orderEntry type="module" module-name="B" />
|
||||
</component>
|
||||
</module>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
public class Main {
|
||||
private void foo() {
|
||||
new MyACla
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module module.main {
|
||||
requires module.b;
|
||||
}
|
||||
+7
-1
@@ -1,7 +1,8 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 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.quickfix
|
||||
|
||||
import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
|
||||
import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor
|
||||
import com.intellij.modcommand.ModCommandAction
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
@@ -43,6 +44,11 @@ class AddModuleDirectiveTest : LightJava9ModulesCodeInsightFixtureTestCase() {
|
||||
"module M { requires M2; }",
|
||||
"module M { requires M2; }")
|
||||
|
||||
fun testNoCircularDependency() {
|
||||
addFile("module-info.java", "module M2 { requires M; }", MultiModuleJava9ProjectDescriptor.ModuleDescriptor.M2)
|
||||
doRequiresTest("module M { }", "module M { }")
|
||||
}
|
||||
|
||||
fun testRequiresInIncompleteModule(): Unit = doRequiresTest(
|
||||
"module M {",
|
||||
"module M {\n" +
|
||||
|
||||
+27
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeInsight.completion;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
@@ -152,9 +152,34 @@ public class JigsawCodeCompletionTest extends LightFixtureCompletionTestCase {
|
||||
}""");
|
||||
}
|
||||
|
||||
public void testCircularDependencyCompletion() {
|
||||
completeBasic("Main.java", """
|
||||
public class Main {
|
||||
private void foo() {
|
||||
new MyACla<caret>
|
||||
}
|
||||
}
|
||||
""")
|
||||
.variants(new Variant("MyAClass", " org.jetbrains.a", Color.RED))
|
||||
.choose(new Variant("MyAClass", " org.jetbrains.a", Color.RED))
|
||||
.check("Main.java", """
|
||||
import org.jetbrains.a.MyAClass;
|
||||
|
||||
public class Main {
|
||||
private void foo() {
|
||||
new MyAClass()
|
||||
}
|
||||
}
|
||||
""")
|
||||
.check("module-info.java", """
|
||||
module module.main {
|
||||
requires module.b;
|
||||
}""");
|
||||
}
|
||||
|
||||
private JigsawCodeCompletionTest variants(Variant @NotNull ... variants) {
|
||||
final LookupElement[] elements = myFixture.getLookupElements();
|
||||
assertEquals(Arrays.toString(elements), elements.length, variants.length);
|
||||
assertEquals(Arrays.toString(elements), variants.length, elements.length);
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
final LookupElementPresentation element = NormalCompletionTestCase.renderElement(elements[i]);
|
||||
assertEquals(variants[i].text(), element.getItemText());
|
||||
|
||||
Reference in New Issue
Block a user