From 3bcffa8c6f39f094f5529f7ffb2f7d08f3bb54b8 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 29 Jul 2025 18:25:04 +0200 Subject: [PATCH] [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 --- .../intention/QuickFixFactory.java | 4 ++ .../analysis/DefaultJavaErrorFixProvider.java | 11 +++++ .../quickfix/AddLibraryDependencyFix.java | 4 +- .../daemon/impl/quickfix/OrderEntryFix.java | 13 ++++++ .../impl/config/QuickFixFactoryImpl.java | 7 +++ .../MultipleModuleHighlightingTest.java | 46 ++++++++++++++++--- .../resources/messages/JavaBundle.properties | 4 +- 7 files changed, 80 insertions(+), 9 deletions(-) diff --git a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java index 7eff8fdb08bc..a1db3d2fb8f7 100644 --- a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java +++ b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java @@ -311,6 +311,10 @@ public abstract class QuickFixFactory { public abstract @NotNull List<@NotNull LocalQuickFix> registerOrderEntryFixes(@NotNull PsiReference reference, @NotNull List registrar); + public abstract @NotNull List<@NotNull LocalQuickFix> registerOrderEntryFixes(@NotNull PsiReference reference, + @NotNull PsiMember target, + @NotNull List registrar); + /** * @param annotationMethods unused, could be empty array */ diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java index e6ed9b86a77b..d937c4ae4238 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java @@ -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 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) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddLibraryDependencyFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddLibraryDependencyFix.java index 24831f3c8932..73ce63e4f5e4 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddLibraryDependencyFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddLibraryDependencyFix.java @@ -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); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java index 68308f7dd51f..14f98ec26835 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java @@ -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 registrar) { + return registerFixes(reference, registrar, shortReferenceName -> new PsiMember[]{target}, r -> r.getCanonicalText()); + } + public static @NotNull List<@NotNull LocalQuickFix> registerFixes(@NotNull PsiReference reference, @NotNull List registrar, @NotNull Function 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(); diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java index 14c199a5adc2..1882fb2ea766 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java @@ -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 registrar) { + return OrderEntryFix.registerFixes(reference, target, registrar); + } + @Override public @NotNull IntentionAction createAddMissingRequiredAnnotationParametersFix(final @NotNull PsiAnnotation annotation, final PsiMethod @NotNull [] annotationMethods, diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/MultipleModuleHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/MultipleModuleHighlightingTest.java index 03126ad77f27..456024774873 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/MultipleModuleHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/MultipleModuleHighlightingTest.java @@ -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; + class C extends B {} + """); + 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("'\\d+'", "'module_name'"); + assertEquals("Adds module 'mod2' to the dependencies of module 'module_name' 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")); diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 96711d5b68fa..ec7959e2ace5 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -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}