[devkit] IJPL-222037 Block new usages of preload services in plugins

GitOrigin-RevId: b38f94f0d99b5733e0d9313bbcab3da81e0f1adb
This commit is contained in:
Yuriy Artamonov
2025-12-22 23:55:33 +00:00
committed by intellij-monorepo-bot
parent 38d2b04dfb
commit 854db13b80
5 changed files with 76 additions and 11 deletions
@@ -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<String> = java.util.Set.of(
"com.intellij.compiler.server.BuildManager",
"com.intellij.openapi.module.WebModuleTypeRegistrar",
@@ -212,6 +212,7 @@ inspections.plugin.xml.product.descriptor.invalid.version=Version must be an int
inspections.plugin.xml.no.plugin.icon.svg.file=<html>Plugin should provide ''{0}'' (<a href="https://plugins.jetbrains.com/docs/intellij/plugin-icon-file.html?from=PluginXmlPluginLogoInspection">Documentation</a>)</html>
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 <postStartupActivity> 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}
@@ -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())) {
@@ -0,0 +1,11 @@
<idea-plugin>
<extensionPoints>
<!--suppress PluginXmlValidity -->
<extensionPoint name="applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<applicationService <error descr="Plugins may not use preloading, use <postStartupActivity> ProjectActivity, lazy MessageBus topics or specific extensions instead.">preload="true"</error>
serviceImplementation="java.lang.String"/>
</extensions>
</idea-plugin>
@@ -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");