[java-intentions] Add OrderEntryFix to 'Cannot access class' error

Fixes IDEA-375037 Cannot access class error should suggest to add dependency on a module to the current

GitOrigin-RevId: 2cc6748f663131eb119b974d6c3699b5f9fcf854
This commit is contained in:
Tagir Valeev
2025-07-30 09:58:00 +00:00
committed by intellij-monorepo-bot
parent 3767ec32bb
commit 3bcffa8c6f
7 changed files with 80 additions and 9 deletions
@@ -311,6 +311,10 @@ public abstract class QuickFixFactory {
public abstract @NotNull List<@NotNull LocalQuickFix> registerOrderEntryFixes(@NotNull PsiReference reference,
@NotNull List<? super IntentionAction> registrar);
public abstract @NotNull List<@NotNull LocalQuickFix> registerOrderEntryFixes(@NotNull PsiReference reference,
@NotNull PsiMember target,
@NotNull List<? super IntentionAction> registrar);
/**
* @param annotationMethods unused, could be empty array
*/
@@ -979,6 +979,17 @@ public final class DefaultJavaErrorFixProvider extends AbstractJavaErrorFixProvi
}
return null;
});
multi(CLASS_NOT_ACCESSIBLE, error -> {
PsiClass aClass = error.context();
String name = aClass.getQualifiedName();
if (name != null) {
PsiJavaCodeReferenceElement ref = JavaPsiFacade.getElementFactory(error.project()).createReferenceFromText(name, error.psi());
List<IntentionAction> fixes = new ArrayList<>();
myFactory.registerOrderEntryFixes(ref, aClass, fixes);
return fixes;
}
return List.of();
});
fix(CLASS_EXTENDS_SEALED_LOCAL, error -> myFactory.createConvertLocalToInnerAction(error.context()));
fix(CLASS_EXTENDS_SEALED_ANOTHER_PACKAGE, error -> {
if (error.context().superClass().getContainingFile() instanceof PsiClassOwner classOwner) {
@@ -131,7 +131,9 @@ class AddLibraryDependencyFix extends OrderEntryFix {
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile psiFile) {
Library firstItem = ContainerUtil.getFirstItem(myLibraries.keySet());
String fqName = myLibraries.get(firstItem);
String refName = !StringUtil.isEmpty(fqName) ? StringUtil.getShortName(fqName) : null;
PsiReference reference = restoreReference();
String refName = reference != null && reference.getElement().isPhysical()
&& !StringUtil.isEmpty(fqName) ? StringUtil.getShortName(fqName) : null;
String libraryList = NlsMessages.formatAndList(ContainerUtil.map(myLibraries.keySet(), library -> "'" + getLibraryName(library) + "'"));
String libraryName = getLibraryName(firstItem);
@@ -23,6 +23,7 @@ import com.intellij.packageDependencies.DependencyValidationManager;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightJavaModule;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.PsiUtil;
@@ -93,6 +94,12 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
}, r -> r.getCanonicalText());
}
public static @NotNull List<@NotNull LocalQuickFix> registerFixes(@NotNull PsiReference reference,
@NotNull PsiMember target,
@NotNull List<? super IntentionAction> registrar) {
return registerFixes(reference, registrar, shortReferenceName -> new PsiMember[]{target}, r -> r.getCanonicalText());
}
public static @NotNull List<@NotNull LocalQuickFix> registerFixes(@NotNull PsiReference reference,
@NotNull List<? super IntentionAction> registrar,
@NotNull Function<? super String, ? extends PsiMember[]> shortReferenceNameToClassesLookup,
@@ -102,6 +109,12 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
Project project = psiElement.getProject();
PsiFile containingFile = psiElement.getContainingFile();
if (containingFile instanceof DummyHolder dh) {
PsiElement context = dh.getContext();
if (context != null) {
containingFile = context.getContainingFile();
}
}
if (containingFile == null) return Collections.emptyList();
VirtualFile refVFile = containingFile.getOriginalFile().getVirtualFile();
if (refVFile == null) return Collections.emptyList();
@@ -720,6 +720,13 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
return OrderEntryFix.registerFixes(reference, registrar);
}
@Override
public @NotNull List<@NotNull LocalQuickFix> registerOrderEntryFixes(@NotNull PsiReference reference,
@NotNull PsiMember target,
@NotNull List<? super IntentionAction> registrar) {
return OrderEntryFix.registerFixes(reference, target, registrar);
}
@Override
public @NotNull IntentionAction createAddMissingRequiredAnnotationParametersFix(final @NotNull PsiAnnotation annotation,
final PsiMethod @NotNull [] annotationMethods,
@@ -1,6 +1,8 @@
// 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.java.codeInsight;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.openapi.module.JavaModuleType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ModuleRootManager;
@@ -58,36 +60,36 @@ public class MultipleModuleHighlightingTest extends JavaCodeInsightFixtureTestCa
ModuleRootModificationUtil.addDependency(mod2, mod1);
myFixture.addFileToProject("mod1/p/A.java", """
package p;\s
package p;
public class A {
public void foo() { /* mod1 A */ }
}
""");
myFixture.addFileToProject("mod1/p/B.java", """
package p;\s
package p;
public class B extends A {
public void foo() { /* mod1 B */ }
}
""");
myFixture.addFileToProject("mod1/p/C.java", """
package p;\s
package p;
public class C extends B {
public void foo() { /* mod1 C */ }
}
""");
myFixture.addFileToProject("mod2/p/A.java", """
package p;\s
package p;
public class A {
public void foo() { /* mod2 A */ }
}
""");
myFixture.addFileToProject("mod2/p/B.java", """
package p;\s
package p;
public class B extends A {
}
""");
PsiFile file = myFixture.addFileToProject("mod2/p/D.java", """
package p;\s
package p;
public class D extends C {
{
super.foo();
@@ -222,6 +224,38 @@ public class MultipleModuleHighlightingTest extends JavaCodeInsightFixtureTestCa
ModuleRootModificationUtil.addDependency(getModule(), mod2);
}
private void addModuleChain() throws IOException {
Module mod1 =
PsiTestUtil.addModule(getProject(), JavaModuleType.getModuleType(), "mod1", myFixture.getTempDirFixture().findOrCreateDir("mod1"));
Module mod2 =
PsiTestUtil.addModule(getProject(), JavaModuleType.getModuleType(), "mod2", myFixture.getTempDirFixture().findOrCreateDir("mod2"));
ModuleRootModificationUtil.addDependency(getModule(), mod1);
ModuleRootModificationUtil.addDependency(mod1, mod2);
}
public void testIndirectClassInaccessible() throws IOException {
addModuleChain();
myFixture.addFileToProject("mod2/p/A.java", """
package p;
public class A {}
""");
myFixture.addFileToProject("mod1/p/B.java", """
package p;
public class B extends A {}
""");
myFixture.configureByText("C.java", """
package p;
<error descr="Cannot access p.A">class C <caret>extends B</error> {}
""");
myFixture.checkHighlighting();
IntentionAction intention = myFixture.findSingleIntention("Add dependency on module 'mod2'");
IntentionPreviewInfo preview = intention.generatePreview(getProject(), myFixture.getEditor(), myFixture.getFile());
IntentionPreviewInfo.Html html = assertInstanceOf(preview, IntentionPreviewInfo.Html.class);
// The current module name is a sequence of digits
String text = html.content().toString().replaceFirst("&#39;\\d+&#39;", "&#39;module_name&#39;");
assertEquals("Adds module &#39;mod2&#39; to the dependencies of module &#39;module_name&#39; and imports unresolved classes if necessary", text);
}
public void testOverridingJdkExceptions() throws IOException {
final Module dep =
PsiTestUtil.addModule(getProject(), JavaModuleType.getModuleType(), "dep", myFixture.getTempDirFixture().findOrCreateDir("dep"));
@@ -1859,8 +1859,8 @@ increase.language.level.preview.description=Language level for module ''{0}'' wi
open.settings.dialog.for.module.preview.text=Open settings dialog for module ''{0}''
button.add.dependency=Add Dependency
adds.ext.library.preview=Adds library ''{0}'' to module ''{1}''
adds.ext.library.preview.import=Adds library ''{0}'' to the dependencies of module ''{1}'' and imports ''{2}''
adds.module.dependencies.preview=Adds {0, choice, 1#module ''''{1}''''|2#one of {2}} to the dependencies of module ''{3}'' and imports unresolved classes
adds.ext.library.preview.import=Adds library ''{0}'' to the dependencies of module ''{1}'' and imports ''{2}'' if necessary
adds.module.dependencies.preview=Adds {0, choice, 1#module ''''{1}''''|2#one of {2}} to the dependencies of module ''{3}'' and imports unresolved classes if necessary
adds.library.preview=Adds {0, choice, 1#library ''''{1}''''|2#one of {2}} to the dependencies of module ''{3}'' and imports unresolved ''{4}''
adds.library.preview.no.import=Adds {0, choice, 1#library ''''{1}''''|2#one of {2}} to the dependencies of module ''{3}''
notification.content.added.annotations=Added {0} {0, choice, 1#annotation|2#annotations}