From 854db13b80e61c1213432dd7c3e5bf068eefad24 Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Sun, 21 Dec 2025 20:43:03 +0100 Subject: [PATCH] [devkit] IJPL-222037 Block new usages of preload services in plugins GitOrigin-RevId: b38f94f0d99b5733e0d9313bbcab3da81e0f1adb --- .../serviceContainer/ComponentManagerImpl.kt | 2 +- .../messages/DevKitBundle.properties | 1 + ...ginXmlExtensionRegistrationInspection.java | 69 ++++++++++++++++--- .../preloadProhibitedService.xml | 11 +++ ...mlExtensionRegistrationInspectionTest.java | 4 ++ 5 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/pluginXmlExtensionRegistration/preloadProhibitedService.xml diff --git a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt index 8c4dadc526ca..27bbd900b152 100644 --- a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt +++ b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt @@ -1489,9 +1489,9 @@ private fun executeRegisterTask(mainPluginDescriptor: IdeaPluginDescriptorImpl, // Ask Core team approve before changing this set @Internal -@VisibleForTesting @Suppress("ReplaceJavaStaticMethodWithKotlinAnalog") @Language("jvm-class-name") +@JvmField val servicePreloadingAllowListForNonCorePlugin: Set = java.util.Set.of( "com.intellij.compiler.server.BuildManager", "com.intellij.openapi.module.WebModuleTypeRegistrar", diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties index 823b8c7d66a7..2b444a2848bd 100644 --- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties +++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties @@ -212,6 +212,7 @@ inspections.plugin.xml.product.descriptor.invalid.version=Version must be an int inspections.plugin.xml.no.plugin.icon.svg.file=Plugin should provide ''{0}'' (Documentation) inspections.plugin.xml.component.interface.class.redundant=Redundant tag 'interface-class' inspections.plugin.xml.service.interface.class.redundant=Redundant attribute 'serviceInterface' +inspections.plugin.xml.service.preload.prohibited=Plugins may not use preloading, use ProjectActivity, lazy MessageBus topics or specific extensions instead. inspections.plugin.xml.duplicated.dependency=Duplicate dependency configuration file ''{0}'' inspections.plugin.xml.ComponentModuleRegistrationChecker.element.registered.wrong.module=Element should be registered in ''{0}'' module where its class ''{1}'' is defined inspections.plugin.xml.ComponentModuleRegistrationChecker.fix.move.registration.name=Move registration to {0} diff --git a/plugins/devkit/devkit-core/src/inspections/PluginXmlExtensionRegistrationInspection.java b/plugins/devkit/devkit-core/src/inspections/PluginXmlExtensionRegistrationInspection.java index 1c790bb0d7e0..82d10109af56 100644 --- a/plugins/devkit/devkit-core/src/inspections/PluginXmlExtensionRegistrationInspection.java +++ b/plugins/devkit/devkit-core/src/inspections/PluginXmlExtensionRegistrationInspection.java @@ -12,12 +12,16 @@ import com.intellij.ide.util.PsiNavigationSupport; import com.intellij.lang.LanguageExtensionPoint; import com.intellij.openapi.components.ServiceDescriptor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiReferenceProviderBean; import com.intellij.psi.impl.source.resolve.reference.PsiReferenceContributorEP; import com.intellij.psi.stubs.StubElementTypeHolderEP; import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.DomElement; @@ -42,6 +46,9 @@ import org.jetbrains.idea.devkit.util.DevKitDomUtil; import java.util.Objects; +import static com.intellij.openapi.project.IntelliJProjectUtil.isIntelliJPlatformProject; +import static com.intellij.serviceContainer.ComponentManagerImplKt.servicePreloadingAllowListForNonCorePlugin; + @ApiStatus.Internal public final class PluginXmlExtensionRegistrationInspection extends DevKitPluginXmlInspectionBase { @@ -94,16 +101,8 @@ public final class PluginXmlExtensionRegistrationInspection extends DevKitPlugin } if (ServiceDescriptor.class.getName().equals(extensionPoint.getBeanClass().getStringValue())) { - GenericAttributeValue serviceInterface = DevKitDomUtil.getAttribute(extension, "serviceInterface"); - GenericAttributeValue serviceImplementation = DevKitDomUtil.getAttribute(extension, "serviceImplementation"); - if (serviceInterface != null && serviceImplementation != null && - StringUtil.equals(serviceInterface.getStringValue(), serviceImplementation.getStringValue())) { - if (hasMissingAttribute(extension, "testServiceImplementation")) { - highlightRedundant(serviceInterface, - DevKitBundle.message("inspections.plugin.xml.service.interface.class.redundant"), - ProblemHighlightType.WARNING, holder); - } - } + checkRedundantServiceInterface(holder, extension); + checkPreloadUseProhibited(holder, extension); return; } @@ -176,6 +175,56 @@ public final class PluginXmlExtensionRegistrationInspection extends DevKitPlugin } } + private static void checkRedundantServiceInterface(@NotNull DomElementAnnotationHolder holder, Extension extension) { + GenericAttributeValue serviceInterface = DevKitDomUtil.getAttribute(extension, "serviceInterface"); + GenericAttributeValue serviceImplementation = DevKitDomUtil.getAttribute(extension, "serviceImplementation"); + if (serviceInterface != null && serviceImplementation != null && + StringUtil.equals(serviceInterface.getStringValue(), serviceImplementation.getStringValue())) { + if (hasMissingAttribute(extension, "testServiceImplementation")) { + highlightRedundant(serviceInterface, + DevKitBundle.message("inspections.plugin.xml.service.interface.class.redundant"), + ProblemHighlightType.WARNING, holder); + } + } + } + + private static void checkPreloadUseProhibited(@NotNull DomElementAnnotationHolder holder, Extension extension) { + GenericAttributeValue preloadAttr = DevKitDomUtil.getAttribute(extension, "preload"); + GenericAttributeValue serviceImplementation = DevKitDomUtil.getAttribute(extension, "serviceImplementation"); + if (preloadAttr != null + && preloadAttr.getStringValue() != null + && serviceImplementation != null + && serviceImplementation.getStringValue() != null + && !isServiceDeclaredInPlatform(holder.getFileElement().getOriginalFile())) { + + var highlightType = servicePreloadingAllowListForNonCorePlugin.contains(serviceImplementation.getStringValue()) + ? ProblemHighlightType.WARNING // existing known problems + : ProblemHighlightType.GENERIC_ERROR; // new usages + + highlightRedundant(preloadAttr, DevKitBundle.message("inspections.plugin.xml.service.preload.prohibited"), highlightType, holder); + } + } + + private static boolean isServiceDeclaredInPlatform(@NotNull XmlFile file) { + var virtualFile = file.getVirtualFile(); + if (virtualFile == null) return false; + + var project = file.getProject(); + if (!isIntelliJPlatformProject(project)) return false; + + var roots = ProjectRootManager.getInstance(project).getContentRoots(); + for (VirtualFile root : roots) { + VirtualFile communityPlatformDir = root.findFileByRelativePath("community/platform"); + VirtualFile platformDir = communityPlatformDir != null ? communityPlatformDir : root.findFileByRelativePath("platform"); + + if (platformDir != null) { + return VfsUtilCore.isAncestor(platformDir, virtualFile, true); + } + } + + return false; + } + private static void checkDefaultBundle(DomElement element, DomElementAnnotationHolder holder) { IdeaPlugin plugin = DomUtil.getParentOfType(element, IdeaPlugin.class, true); if (plugin != null && !DomUtil.hasXml(plugin.getResourceBundle())) { diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/pluginXmlExtensionRegistration/preloadProhibitedService.xml b/plugins/devkit/devkit-java-tests/testData/inspections/pluginXmlExtensionRegistration/preloadProhibitedService.xml new file mode 100644 index 000000000000..69e369c91a07 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/pluginXmlExtensionRegistration/preloadProhibitedService.xml @@ -0,0 +1,11 @@ + + + + + + + + preload="true" + serviceImplementation="java.lang.String"/> + + \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlExtensionRegistrationInspectionTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlExtensionRegistrationInspectionTest.java index eba8cc712fad..d512c69bfe27 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlExtensionRegistrationInspectionTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlExtensionRegistrationInspectionTest.java @@ -86,6 +86,10 @@ public class PluginXmlExtensionRegistrationInspectionTest extends JavaCodeInsigh myFixture.testHighlighting("redundantServiceInterfaceClass.xml"); } + public void testPreloadPreloadProhibitedService() { + myFixture.testHighlighting("preloadProhibitedService.xml"); + } + public void testIntentionActionDescriptionDirectoryName() { myFixture.addClass("public class MyIntentionAction {}"); myFixture.testHighlighting("intentionActionDescriptionDirectoryName.xml");