[devkit] IJPL-185820: Report com.intellij.* plugin aliases for non-JetBrains plugins

GitOrigin-RevId: 26c9c810566975039824615253eebf1e9332a21b
This commit is contained in:
Karol Lewandowski
2025-11-14 09:42:14 +00:00
committed by intellij-monorepo-bot
parent 7bf4fae01e
commit bd1ae3e686
7 changed files with 162 additions and 1 deletions
@@ -0,0 +1,11 @@
<html>
<body>
<p>
Reports restricted plugin module aliases in non-JetBrains plugin descriptors.
</p>
<p>
Aliases starting with <code>com.intellij.*</code> are reserved for plugins developed by JetBrains and must not be declared
by third-party plugins.
</p>
</body>
</html>
@@ -551,6 +551,13 @@
implementationClass="org.jetbrains.idea.devkit.inspections.ContentModuleNamespaceInspection"
key="inspection.content.module.namespace.name"/>
<localInspection language="XML" applyToDialects="false"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path" groupKey="inspections.group.descriptor"
enabledByDefault="true" level="ERROR"
implementationClass="org.jetbrains.idea.devkit.inspections.WrongPluginModuleAliasInspection"
key="inspection.plugin.module.alias.name"/>
<localInspection language="UAST"
projectType="INTELLIJ_PLUGIN"
enabledByDefault="true"
@@ -837,4 +837,6 @@ plugins.php.description=PHP language
plugins.ruby.description=Ruby language
plugins.rust.description=Rust language
inspection.use.optimized.eel.functions.display.name=Use optimized function from EelApi
inspections.plugin.keys.in.platform.registry=Plugin keys defined in platform regitry.properties
inspections.plugin.keys.in.platform.registry=Plugin keys defined in platform regitry.properties
inspection.plugin.module.alias.name=Invalid plugin module alias
inspection.plugin.module.alias.message=Plugin module alias must not start with 'com.intellij.*' in non-JetBrains plugins
@@ -0,0 +1,35 @@
// 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.idea.devkit.inspections
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.project.IntelliJProjectUtil
import com.intellij.util.xml.DomElement
import com.intellij.util.xml.DomUtil
import com.intellij.util.xml.GenericAttributeValue
import com.intellij.util.xml.highlighting.DomElementAnnotationHolder
import com.intellij.util.xml.highlighting.DomHighlightingHelper
import org.jetbrains.idea.devkit.DevKitBundle.message
import org.jetbrains.idea.devkit.dom.IdeaPlugin
import org.jetbrains.idea.devkit.dom.PluginModule
internal class WrongPluginModuleAliasInspection : DevKitPluginXmlInspectionBase() {
private val reservedPrefix = "com.intellij."
override fun checkDomElement(element: DomElement, holder: DomElementAnnotationHolder, helper: DomHighlightingHelper) {
val module = element as? PluginModule ?: return
if (IntelliJProjectUtil.isIntelliJPlatformProject(module.xmlElement?.project) || isDevelopedByJetBrains(element)) return
val value: GenericAttributeValue<String> = module.value
val alias = value.value ?: return
if (alias.startsWith(reservedPrefix)) {
holder.createProblem(value, message("inspection.plugin.module.alias.message"))
}
}
private fun isDevelopedByJetBrains(element: PluginModule): Boolean {
val vendor = DomUtil.findDomElement(element.xmlElement, IdeaPlugin::class.java)?.vendor ?: return false
return PluginManagerCore.isDevelopedByJetBrains(vendor.stringValue)
}
}
@@ -47,6 +47,7 @@ jvm_library(
"//plugins/devkit/devkit-tests:testFramework_test_lib",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
"//platform/testFramework/junit5/codeInsight",
"//java/compiler:compiler-tests_test_lib",
"//java/testFramework",
"//xml/dom-tests:dom-tests_test_lib",
@@ -33,6 +33,7 @@
<orderEntry type="module" module-name="intellij.platform.usageView" />
<orderEntry type="module" module-name="intellij.devkit.testFramework" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5.codeInsight" />
<orderEntry type="module" module-name="intellij.java.compiler.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.xml.dom.tests" scope="TEST" />
@@ -0,0 +1,104 @@
// 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.idea.devkit.inspections
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.project.IntelliJProjectUtil
import com.intellij.openapi.project.Project
import com.intellij.platform.testFramework.junit5.codeInsight.fixture.codeInsightFixture
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.fixture.TestFixture
import com.intellij.testFramework.junit5.fixture.moduleFixture
import com.intellij.testFramework.junit5.fixture.projectFixture
import com.intellij.testFramework.junit5.fixture.tempPathFixture
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.nio.file.Path
import java.util.stream.Stream
import kotlin.streams.asStream
@TestApplication
class WrongPluginModuleAliasInspectionTest {
private val tempDir: TestFixture<Path> = tempPathFixture()
private val project: TestFixture<Project> = projectFixture(tempDir, openAfterCreation = true)
@Suppress("unused") // required by codeInsightFixture
private val module by project.moduleFixture(tempDir, addPathToSourceRoot = true)
private val fixture = codeInsightFixture(project, tempDir)
@BeforeEach
fun setUp() {
fixture.get().enableInspections(WrongPluginModuleAliasInspection())
}
@DisplayName("Plugin module alias inspection test")
@ParameterizedTest(name = "{0} when vendor is ''{1}'', module prefix is ''{2}'', and IntelliJ Platform project is {3}")
@MethodSource("testCases")
fun testPluginModuleAlias(
testExpectationPrefix: String,
vendor: String,
modulePrefix: String,
isIntelliJProject: Boolean,
) {
if (isIntelliJProject) {
IntelliJProjectUtil.markAsIntelliJPlatformProject(project.get(), true)
}
val moduleValue = "${modulePrefix}myFeature"
val errorTag = if (testExpectationPrefix == ERROR_EXPECTED) {
"""<error descr="Plugin module alias must not start with 'com.intellij.*' in non-JetBrains plugins">$moduleValue</error>"""
}
else {
moduleValue
}
doTest("""
<idea-plugin>
<vendor>$vendor</vendor>
<module value="$errorTag"/>
</idea-plugin>
""".trimIndent())
}
private fun doTest(@Language("XML") xml: String) {
fixture.get().apply {
configureByText("plugin.xml", xml)
testHighlighting(false, false, true)
}
}
companion object {
private const val ERROR_EXPECTED = "should report error"
private const val ERROR_NOT_EXPECTED = "should not report error"
@JvmStatic
fun testCases(): Stream<Arguments> =
cartesianProduct(
listOf("JetBrains", "JetBrains s.r.o.", "JetBrains, Google", "Acme"),
listOf("com.intellij.", "com.example."),
listOf(true, false)
).map { (vendor, prefix, isIntelliJProject) ->
val expectError = !PluginManagerCore.isDevelopedByJetBrains(vendor) && prefix == "com.intellij." && !isIntelliJProject
val testExpectationPrefix = if (expectError) ERROR_EXPECTED else ERROR_NOT_EXPECTED
Arguments.of(testExpectationPrefix, vendor, prefix, isIntelliJProject)
}.asStream()
private fun <A, B, C> cartesianProduct(
listA: List<A>,
listB: List<B>,
listC: List<C>,
): Sequence<Triple<A, B, C>> = sequence {
for (a in listA) {
for (b in listB) {
for (c in listC) {
yield(Triple(a, b, c))
}
}
}
}
}
}