From 9252254775ebac7474079d116dfb54b5df217f61 Mon Sep 17 00:00:00 2001 From: "Alexander.Glukhov" Date: Thu, 25 Sep 2025 15:39:09 +0200 Subject: [PATCH] [gradle][IDEA-378369] do not show the "Download Sources" button in the editor for libraries provided by Gradle GitOrigin-RevId: edb93cd092c44aac4348c80381b3cfcd83700038 --- .../AttachSourcesNotificationProvider.java | 11 ++- .../resources/META-INF/JavaPlugin.xml | 2 + .../AttachSourcesProviderFilter.kt | 31 +++++++ .../gradle/java/resources/META-INF/plugin.xml | 1 + .../GradleAttachSourcesProviderFilter.kt | 16 ++++ .../GradleAttachSourcesProviderFilterTest.kt | 91 +++++++++++++++++++ 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 java/openapi/src/com/intellij/codeInsight/AttachSourcesProviderFilter.kt create mode 100644 plugins/gradle/java/src/editor/GradleAttachSourcesProviderFilter.kt create mode 100644 plugins/gradle/java/testSources/editor/GradleAttachSourcesProviderFilterTest.kt diff --git a/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AttachSourcesNotificationProvider.java b/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AttachSourcesNotificationProvider.java index 02c0a3bcc1f9..dbaf5ad20c88 100644 --- a/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AttachSourcesNotificationProvider.java +++ b/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AttachSourcesNotificationProvider.java @@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.CommonBundle; import com.intellij.codeEditor.JavaEditorFileSwapper; import com.intellij.codeInsight.AttachSourcesProvider; +import com.intellij.codeInsight.AttachSourcesProviderFilter; import com.intellij.ide.JavaUiBundle; import com.intellij.ide.highlighter.JavaClassFileType; import com.intellij.ide.highlighter.JavaFileType; @@ -52,9 +53,7 @@ import com.intellij.util.concurrency.NonUrgentExecutor; import com.intellij.util.concurrency.annotations.RequiresBackgroundThread; import com.intellij.util.concurrency.annotations.RequiresEdt; import com.intellij.util.concurrency.annotations.RequiresReadLock; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.*; import javax.swing.*; import java.io.ByteArrayInputStream; @@ -68,7 +67,8 @@ import java.util.function.Function; /** * @author Dmitry Avdeev */ -final class AttachSourcesNotificationProvider implements EditorNotificationProvider { +@VisibleForTesting +public final class AttachSourcesNotificationProvider implements EditorNotificationProvider { private static final ExtensionPointName EXTENSION_POINT_NAME = new ExtensionPointName<>("com.intellij.attachSourcesProvider"); @@ -194,6 +194,9 @@ final class AttachSourcesNotificationProvider implements EditorNotificationProvi boolean hasNonLightAction = false; for (AttachSourcesProvider provider : EXTENSION_POINT_NAME.getExtensionList()) { + if (!AttachSourcesProviderFilter.isProviderApplicable(provider, libraries, classFile)) { + continue; + } for (AttachSourcesProvider.AttachSourcesAction action : provider.getActions(libraries, classFile)) { if (hasNonLightAction) { if (action instanceof AttachSourcesProvider.LightAttachSourcesAction) { diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 412a9cbeaf9b..adac70de58d7 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -214,6 +214,8 @@ + diff --git a/java/openapi/src/com/intellij/codeInsight/AttachSourcesProviderFilter.kt b/java/openapi/src/com/intellij/codeInsight/AttachSourcesProviderFilter.kt new file mode 100644 index 000000000000..c8a6ee0a5682 --- /dev/null +++ b/java/openapi/src/com/intellij/codeInsight/AttachSourcesProviderFilter.kt @@ -0,0 +1,31 @@ +// 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 + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.psi.PsiFile +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +interface AttachSourcesProviderFilter { + + /** + * Checks can be an instance of AttachSourcesProvider applied to the specific PsiFile. + */ + fun isApplicable(provider: AttachSourcesProvider, orderEntries: List, psiFile: PsiFile): Boolean + + companion object { + private val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.attachSourcesProviderFilter") + + @JvmStatic + fun isProviderApplicable(provider: AttachSourcesProvider, orderEntries: List, psiFile: PsiFile): Boolean { + var applicable = true + EP_NAME.forEachExtensionSafe { + if (!it.isApplicable(provider, orderEntries, psiFile)) { + applicable = false + } + } + return applicable + } + } +} \ No newline at end of file diff --git a/plugins/gradle/java/resources/META-INF/plugin.xml b/plugins/gradle/java/resources/META-INF/plugin.xml index 88ec4e9ef917..070e1012d34d 100644 --- a/plugins/gradle/java/resources/META-INF/plugin.xml +++ b/plugins/gradle/java/resources/META-INF/plugin.xml @@ -72,6 +72,7 @@ serviceImplementation="org.jetbrains.plugins.gradle.GradleJavaIdeManager"/> + diff --git a/plugins/gradle/java/src/editor/GradleAttachSourcesProviderFilter.kt b/plugins/gradle/java/src/editor/GradleAttachSourcesProviderFilter.kt new file mode 100644 index 000000000000..ce2bd01072dc --- /dev/null +++ b/plugins/gradle/java/src/editor/GradleAttachSourcesProviderFilter.kt @@ -0,0 +1,16 @@ +// 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.gradle.editor + +import com.intellij.codeInsight.AttachSourcesProvider +import com.intellij.codeInsight.AttachSourcesProviderFilter +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.psi.PsiFile +import org.jetbrains.plugins.gradle.util.GradleConstants + +class GradleAttachSourcesProviderFilter : AttachSourcesProviderFilter { + + override fun isApplicable(provider: AttachSourcesProvider, orderEntries: List, psiFile: PsiFile): Boolean { + return orderEntries.mapNotNull { it.library?.externalSource?.id } + .none { GradleConstants.SYSTEM_ID.id == it } + } +} \ No newline at end of file diff --git a/plugins/gradle/java/testSources/editor/GradleAttachSourcesProviderFilterTest.kt b/plugins/gradle/java/testSources/editor/GradleAttachSourcesProviderFilterTest.kt new file mode 100644 index 000000000000..3a6b34bd5687 --- /dev/null +++ b/plugins/gradle/java/testSources/editor/GradleAttachSourcesProviderFilterTest.kt @@ -0,0 +1,91 @@ +// 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.gradle.editor + +import com.intellij.codeInsight.daemon.impl.AttachSourcesNotificationProvider +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.openapi.roots.OrderRootType +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.testFramework.IndexingTestUtil +import com.intellij.ui.EditorNotificationPanel +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.plugins.gradle.importing.GradleImportingTestCase +import org.jetbrains.plugins.gradle.testFramework.util.importProject +import org.junit.Test +import org.mockito.Mockito +import java.awt.Container +import java.util.function.Consumer +import kotlin.io.path.deleteIfExists + +class GradleAttachSourcesProviderFilterTest : GradleImportingTestCase() { + + private companion object { + private const val DEPENDENCY = "junit:junit:4.12" + private const val DEPENDENCY_NAME = "Gradle: junit:junit:4.12" + private const val DEPENDENCY_JAR = "junit-4.12.jar" + private const val DEPENDENCY_SOURCES_JAR = "junit-4.12-sources.jar" + private const val CLASS_FROM_DEPENDENCY = "junit.framework.Test" + private const val DEPENDENCY_SOURCES_JAR_CACHE_PATH = "caches/modules-2/files-2.1/junit/junit/4.12/" + + "a6c32b40bf3d76eca54e3c601e5d1470c86fcdfa/$DEPENDENCY_SOURCES_JAR" + } + + override fun setUp() { + super.setUp() + removeCachedLibrary() + } + + @Test + fun `test only one default attach sources button is shown for a gradle library`(): Unit = runBlocking { + importProject { + withJavaPlugin() + withIdeaPlugin() + withMavenCentral() + addTestImplementationDependency(DEPENDENCY) + addPrefix("idea.module.downloadSources = false") + } + + assertModules("project", "project.main", "project.test") + + assertLibraryOrderEntry("project.test", DEPENDENCY_NAME) { + assertProductionClasses(DEPENDENCY_JAR) + assertNoSources() + } + + IndexingTestUtil.waitUntilIndexesAreReady(myProject) + + val provider = AttachSourcesNotificationProvider() + runReadAction { + val psiFile = JavaPsiFacade.getInstance(myProject) + .findClass(CLASS_FROM_DEPENDENCY, GlobalSearchScope.allScope(myProject))!! + .containingFile + .virtualFile + val component = provider.collectNotificationData(myProject, psiFile)!!.apply(Mockito.mock()) as EditorNotificationPanel + assertEquals("Unexpected notification panel layout. Please fix the test accordingly to the expected layout", 2, component.components.size) + val actionPanel = component.components[1] as Container + assertEquals("Only one action should be available", 1, actionPanel.components.size) + } + } + + private fun assertLibraryOrderEntry(moduleName: String, dependencyName: String, fn: LibraryOrderEntry.() -> Unit) { + val libraries = getModuleLibDeps(moduleName, dependencyName) + assertThat(libraries).hasSize(1) + val libraryOrderEntry = libraries.first() + fn(libraryOrderEntry) + } + + private fun LibraryOrderEntry.assertProductionClasses(jarName: String) { + assertThat(getRootFiles(OrderRootType.CLASSES)) + .hasSize(1) + .allSatisfy(Consumer { assertEquals(jarName, it.name) }) + } + + private fun LibraryOrderEntry.assertNoSources() { + assertThat(getRootFiles(OrderRootType.SOURCES)).hasSize(0) + } + + private fun removeCachedLibrary(cachePath: String = DEPENDENCY_SOURCES_JAR_CACHE_PATH) = gradleUserHome.resolve(cachePath).run { + deleteIfExists() + } +} \ No newline at end of file