[java] Consider candidates from all class finders in class finder

This will make sure the best class is always found. For example, when there are multiple dependencies that contain the same class, the class from the first dependency should be picked, even if it only is found by a PsiElementFinder that appears later in the chain. #IDEA-342753 Fixed


(cherry picked from commit fcd905b1cea47dd488664eff62e2ff1049c4e336)

IJ-MR-155802

GitOrigin-RevId: 7d99cc055ddb03678d162f471dfb0e4fb46cd0a3
This commit is contained in:
Bart van Helvert
2025-03-06 20:18:59 +00:00
committed by intellij-monorepo-bot
parent e078b269b6
commit 2342dbb3ff
3 changed files with 52 additions and 8 deletions
@@ -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.psi.impl;
import com.intellij.lang.jvm.JvmClass;
@@ -23,6 +23,7 @@ import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiClassUtil;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.Processor;
@@ -103,18 +104,20 @@ public final class JavaPsiFacadeImpl extends JavaPsiFacadeEx {
}
List<PsiElementFinder> finders = filteredFinders();
Predicate<PsiClass> classesFilter = getFilterFromFinders(scope, finders);
PsiClass bestClass = null;
for (PsiElementFinder finder : finders) {
try {
PsiClass aClass = finder.findClass(qualifiedName, scope);
if (aClass != null && (classesFilter == null || classesFilter.test(aClass))) {
return aClass;
}
PsiClass candidateClass = finder.findClass(qualifiedName, scope);
if (candidateClass == null) continue;
if (classesFilter != null && !classesFilter.test(candidateClass)) continue;
if (bestClass != null && PsiClassUtil.createScopeComparator(scope).compare(candidateClass, bestClass) >= 0) continue;
bestClass = candidateClass;
}
catch (IndexNotReadyException ex) {
handleIndexNotReadyException(ex);
}
}
return null;
return bestClass;
}
private PsiClass @NotNull [] findClassesInDumbMode(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
@@ -1,11 +1,11 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.psi.resolve;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.*;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -134,6 +134,7 @@ public class ResolveClassInModulesWithDependenciesTest extends JavaResolveTestCa
PsiElementFinder.class.getDeclaredMethods(),
m -> !ignoredMethods.contains(m.getName()) && !Modifier.isPrivate(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()));
PsiElementFinder mock = createMockBuilder(PsiElementFinder.class).addMockedMethods(methods).createMock();
expect(mock.findClass(anyObject(), anyObject())).andReturn(null).anyTimes();
expect(mock.findClasses(anyObject(), anyObject())).andReturn(PsiClass.EMPTY_ARRAY).anyTimes();
expect(mock.findPackage(eq("foo"))).andReturn(null);
expect(mock.getSubPackages(rootPackage(), anyObject())).andReturn(PsiPackage.EMPTY_ARRAY);
@@ -0,0 +1,40 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.groovy.lang.resolve
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.psi.PsiReference
import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
class GroovyMultiModuleResolveTest : JavaCodeInsightFixtureTestCase() {
fun `test same class from different modules`() {
val groovyModule = PsiTestUtil.addModule(project, StdModuleTypes.JAVA, "groovyModule", myFixture.tempDirFixture.findOrCreateDir("groovyModule"))
myFixture.addFileToProject("groovyModule/foo/Foo.groovy", """
package foo
class Foo {}
""".trimIndent())
ModuleRootModificationUtil.addDependency(myFixture.module, groovyModule)
val javaModule = PsiTestUtil.addModule(project, StdModuleTypes.JAVA, "javaModule", myFixture.tempDirFixture.findOrCreateDir("javaModule"))
myFixture.addFileToProject("javaModule/foo/Foo.java", """
package foo;
public class Foo {}
""".trimIndent())
ModuleRootModificationUtil.addDependency(myFixture.module, javaModule)
myFixture.configureByText("Main.groovy", """
import foo.Foo
class Main {
F<caret>oo foo = new Foo()
}
""".trimIndent())
val resolved = (myFixture.file.findElementAt(myFixture.caretOffset)?.parent as? PsiReference)?.resolve()
// resolve to Groovy because dependency was added first and has higher priority
assertEquals("Foo.groovy", resolved?.containingFile?.name)
}
}