[devkit] IJPL-209927 Check that content module used as a dependency is visible

GitOrigin-RevId: 9fd75e092e8e4b247afbd9a42dec5058bc2f8f56
This commit is contained in:
Karol Lewandowski
2025-10-13 18:01:18 +00:00
committed by intellij-monorepo-bot
parent 82d4de3102
commit 600fcb4715
6 changed files with 856 additions and 3 deletions
@@ -0,0 +1,16 @@
<html>
<body>
Reports violations of content module visibility when declaring module dependencies.
<p>
Content modules can specify their visibility as <code>private</code> (default, when missing), <code>internal</code>, or <code>public</code>.
</p>
<p>
When declaring dependencies on other modules, the following rules must be followed:
</p>
<ul>
<li>Private modules can only be accessed within the same plugin.</li>
<li>Internal modules can only be accessed when declared within the same namespace (<code>content@namespace</code>).</li>
<li>Public modules can be accessed from anywhere.</li>
</ul>
</body>
</html>
@@ -536,6 +536,13 @@
implementationClass="org.jetbrains.idea.devkit.inspections.ModuleNotRegisteredAsPluginContentInspection"
key="inspection.module.not.registered.as.plugin.content.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.ContentModuleVisibilityInspection"
key="inspection.content.module.visibility.name"/>
<localInspection language="XML" applyToDialects="false"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path" groupKey="inspections.group.descriptor"
@@ -774,6 +774,12 @@ inspection.module.not.registered.as.plugin.content.name=Module is not declared a
inspection.module.not.registered.as.plugin.content.message=The ''{0}'' module is not declared as content in any plugin descriptor
inspection.module.not.registered.as.plugin.content.fix.add=Register as content module of the ''{0}'' plugin
inspection.content.module.visibility.name=Cannot access content module dependency
inspection.content.module.visibility.internal=The ''{0}'' module is internal and declared in namespace ''{1}'' in ''{2}'', so it cannot be accessed from module ''{3}'', which is declared in namespace ''{4}'' in ''{5}''
inspection.content.module.visibility.internal.dependency.namespace.missing=The ''{0}'' module is internal and declared without namespace in ''{1}'', so it cannot be accessed from module ''{2}'', which is declared in namespace ''{3}'' in ''{4}''
inspection.content.module.visibility.internal.current.namespace.missing=The ''{0}'' module is internal and declared in namespace ''{1}'' in ''{2}'', so it cannot be accessed from module ''{3}'', which is declared without namespace in ''{4}''
inspection.content.module.visibility.private=The ''{0}'' module is private and declared in plugin ''{1}'', so it cannot be accessed from module ''{2}'' declared in plugin ''{3}''
inspection.content.module.namespace.name=Content module namespace validation
inspection.content.module.namespace.missing=Namespace is required for 'content' with non-private modules
inspection.content.module.namespace.mismatch=All 'content' elements must define the same namespace (first namespace: ''{0}'')
@@ -0,0 +1,196 @@
// 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.highlighter.XmlFileType
import com.intellij.openapi.fileEditor.UniqueVFilePathBuilder
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.GlobalSearchScopesCore
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.parentOfType
import com.intellij.psi.xml.XmlFile
import com.intellij.psi.xml.XmlTag
import com.intellij.util.xml.DomElement
import com.intellij.util.xml.GenericAttributeValue
import com.intellij.util.xml.highlighting.DomElementAnnotationHolder
import com.intellij.util.xml.highlighting.DomHighlightingHelper
import com.intellij.xml.util.XmlUtil
import org.jetbrains.annotations.Nls
import org.jetbrains.idea.devkit.DevKitBundle.message
import org.jetbrains.idea.devkit.dom.ContentModuleVisibility
import org.jetbrains.idea.devkit.dom.DependencyDescriptor
import org.jetbrains.idea.devkit.dom.IdeaPlugin
import org.jetbrains.idea.devkit.dom.index.PluginIdDependenciesIndex
import org.jetbrains.idea.devkit.util.DescriptorUtil
internal class ContentModuleVisibilityInspection : DevKitPluginXmlInspectionBase() {
override fun checkDomElement(element: DomElement, holder: DomElementAnnotationHolder, helper: DomHighlightingHelper) {
val dependencyModule = element as? DependencyDescriptor.ModuleDescriptor ?: return
val dependencyName = dependencyModule.name
val dependency = dependencyName.value ?: return
val dependencyVisibility = dependency.contentModuleVisibility
when (dependencyVisibility.value ?: ContentModuleVisibility.PRIVATE) {
ContentModuleVisibility.PUBLIC -> return // can be accessed from anywhere
ContentModuleVisibility.INTERNAL -> checkInternalVisibility(dependencyName, dependency, holder)
ContentModuleVisibility.PRIVATE -> checkPrivateVisibility(dependencyName, dependency, holder)
}
}
private fun checkInternalVisibility(
dependencyValue: GenericAttributeValue<IdeaPlugin?>,
moduleDependency: IdeaPlugin,
holder: DomElementAnnotationHolder,
) {
val currentXmlFile = dependencyValue.xmlElement?.containingFile as? XmlFile ?: return
val productionXmlFilesScope = getProjectProductionXmlFilesScope(currentXmlFile.project)
val currentModuleIncludingPlugins = getPluginXmlFilesIncludingFileAsContentModule(currentXmlFile, productionXmlFilesScope)
val dependencyXmlFile = moduleDependency.xmlElement?.containingFile as? XmlFile ?: return
val dependencyIncludingPlugins = getPluginXmlFilesIncludingFileAsContentModule(dependencyXmlFile, productionXmlFilesScope)
for (currentModuleIncludingPlugin in currentModuleIncludingPlugins) {
for (dependencyIncludingPlugin in dependencyIncludingPlugins) {
if (currentModuleIncludingPlugin == dependencyIncludingPlugin) continue
val currentModuleNamespace = currentModuleIncludingPlugin.getNamespace()
val dependencyNamespace = dependencyIncludingPlugin.getNamespace()
if (currentModuleNamespace != dependencyNamespace) {
holder.createProblem(
dependencyValue,
getInternalVisibilityProblemMessage(dependencyValue, dependencyIncludingPlugin, currentXmlFile, currentModuleIncludingPlugin)
)
return // report only one problem at once
}
}
}
}
private fun getInternalVisibilityProblemMessage(
dependencyValue: GenericAttributeValue<IdeaPlugin?>,
dependencyIncludingPlugin: IdeaPlugin,
currentXmlFile: XmlFile,
currentModuleIncludingPlugin: IdeaPlugin,
): @Nls String? {
val dependencyNamespace = dependencyIncludingPlugin.getNamespace()
val currentModuleNamespace = currentModuleIncludingPlugin.getNamespace()
return when {
dependencyNamespace == null -> message(
"inspection.content.module.visibility.internal.dependency.namespace.missing",
dependencyValue.stringValue, dependencyIncludingPlugin.getUniqueFileName(),
getModuleName(currentXmlFile), currentModuleNamespace, currentModuleIncludingPlugin.getUniqueFileName()
)
currentModuleNamespace == null -> message(
"inspection.content.module.visibility.internal.current.namespace.missing",
dependencyValue.stringValue, dependencyNamespace, dependencyIncludingPlugin.getUniqueFileName(),
getModuleName(currentXmlFile), currentModuleIncludingPlugin.getUniqueFileName()
)
else -> message(
"inspection.content.module.visibility.internal",
dependencyValue.stringValue, dependencyNamespace, dependencyIncludingPlugin.getUniqueFileName(),
getModuleName(currentXmlFile), currentModuleNamespace, currentModuleIncludingPlugin.getUniqueFileName()
)
}
}
private fun getPluginXmlFilesIncludingFileAsContentModule(xmlFile: XmlFile, scope: GlobalSearchScope): Collection<IdeaPlugin> {
val moduleVirtualFile = xmlFile.virtualFile ?: return emptyList()
val psiManager = xmlFile.manager
return findFilesIncludingContentModule(moduleVirtualFile, scope)
.mapNotNull { psiManager.findFile(it) as? XmlFile }
.mapNotNull { DescriptorUtil.getIdeaPlugin(it) }
}
private fun findFilesIncludingContentModule(moduleVirtualFile: VirtualFile, scope: GlobalSearchScope): Collection<VirtualFile> {
return PluginIdDependenciesIndex.findFilesIncludingContentModule(moduleVirtualFile, scope)
}
private fun IdeaPlugin.getNamespace(): String? {
// all <content> must have the same namespace, so take it from the first:
return this.content.firstOrNull()?.namespace?.value
}
private fun IdeaPlugin.getUniqueFileName(): String {
return UniqueVFilePathBuilder.getInstance().getUniqueVirtualFilePath(xmlElement!!.project, xmlElement!!.containingFile.virtualFile)
}
private fun checkPrivateVisibility(
dependencyValue: GenericAttributeValue<IdeaPlugin?>,
moduleDependency: IdeaPlugin,
holder: DomElementAnnotationHolder,
) {
val currentXmlFile = dependencyValue.xmlElement?.containingFile as? XmlFile ?: return
val project = currentXmlFile.project
val productionXmlFilesScope = getProjectProductionXmlFilesScope(project)
val currentModuleIncludingPlugins = getPluginsIncludingFileAsContentModule(currentXmlFile, productionXmlFilesScope)
val dependencyXmlFile = moduleDependency.xmlElement?.containingFile as? XmlFile ?: return
val dependencyIncludingPlugins = getPluginsIncludingFileAsContentModule(dependencyXmlFile, productionXmlFilesScope)
for (currentModuleIncludingPlugin in currentModuleIncludingPlugins) {
if (dependencyIncludingPlugins.contains(currentModuleIncludingPlugin)) continue // are included in the same plugin
for (dependencyIncludingPlugin in dependencyIncludingPlugins) {
if (currentModuleIncludingPlugin != dependencyIncludingPlugin) {
holder.createProblem(
dependencyValue,
message(
"inspection.content.module.visibility.private",
getModuleName(dependencyXmlFile), dependencyIncludingPlugin.getIdOrUniqueFileName(),
getModuleName(currentXmlFile), currentModuleIncludingPlugin.getIdOrUniqueFileName()
)
)
return // report only one problem at once
}
}
}
}
private fun getProjectProductionXmlFilesScope(project: Project): GlobalSearchScope {
return GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScopesCore.projectProductionScope(project), XmlFileType.INSTANCE)
}
private fun getPluginsIncludingFileAsContentModule(xmlFile: XmlFile, scope: GlobalSearchScope): Collection<IdeaPlugin> {
val moduleVirtualFile = xmlFile.virtualFile ?: return emptyList()
val psiManager = xmlFile.manager
return findFilesIncludingContentModule(moduleVirtualFile, scope)
.mapNotNull { psiManager.findFile(it) as? XmlFile }
.flatMap { getActualIncludingPlugins(it, scope) }
.distinct()
}
/**
* If [xmlFile]:
* - has ID or is `META-INF/plugin.xml`, then return it, as it is an actual plugin
* - is included via `<xi:include>`, find including plugins (recursively).
*/
private fun getActualIncludingPlugins(
xmlFile: XmlFile,
scope: GlobalSearchScope,
visited: MutableSet<XmlFile> = mutableSetOf(),
): Collection<IdeaPlugin> {
if (!visited.add(xmlFile)) return emptyList() // prevent inclusion cycles
val ideaPlugin = DescriptorUtil.getIdeaPlugin(xmlFile)
if (ideaPlugin != null && isActualPluginDescriptor(ideaPlugin, xmlFile)) {
return listOf(ideaPlugin)
}
return ReferencesSearch.search(xmlFile, scope)
.filtering { isXiIncluded(it) }
.findAll()
.flatMap { getActualIncludingPlugins(it.element.containingFile as XmlFile, scope, visited) }
}
private fun isActualPluginDescriptor(ideaPlugin: IdeaPlugin, xmlFile: XmlFile): Boolean =
ideaPlugin.pluginId != null || (xmlFile.name == "plugin.xml" && xmlFile.parent?.name == "META-INF")
private fun isXiIncluded(reference: PsiReference): Boolean {
val xmlTag = reference.element.parentOfType<XmlTag>() ?: return false
return xmlTag.namespace == XmlUtil.XINCLUDE_URI && xmlTag.localName == "include"
}
private fun getModuleName(xmlFile: XmlFile): String {
return xmlFile.virtualFile.nameWithoutExtension
}
private fun IdeaPlugin.getIdOrUniqueFileName(): String {
return pluginId ?: getUniqueFileName()
}
}
@@ -0,0 +1,629 @@
// 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.psi.PsiFile
import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
import org.intellij.lang.annotations.Language
import org.jetbrains.idea.devkit.module.PluginModuleType
class ContentModuleVisibilityInspectionTest : JavaCodeInsightFixtureTestCase() {
override fun setUp() {
super.setUp()
// it is required for correct recognizing if the project is a plugin project (see PsiUtil.IDE_PROJECT_MARKER_CLASS):
myFixture.addClass("package com.intellij.ui.components; public class JBList {}")
myFixture.enableInspections(ContentModuleVisibilityInspection())
}
fun `test should report private module dependency from internal module`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.privatemodule",
"com.example.plugin.with.privatemodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.privatemodule</id>
<content>
<module name="com.example.privatemodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.internalmodule",
"com.example.plugin.with.internalmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.internalmodule</id>
<content>
<module name="com.example.internalmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.internalmodule",
"com.example.internalmodule/com.example.internalmodule.xml",
"""
<idea-plugin visibility="internal">
<dependencies>
<module name="<error descr="The 'com.example.privatemodule' module is private and declared in plugin 'com.example.plugin.with.privatemodule', so it cannot be accessed from module 'com.example.internalmodule' declared in plugin 'com.example.plugin.with.internalmodule'">com.example.privatemodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should report private module dependency from public module`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.privatemodule",
"com.example.plugin.with.privatemodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.privatemodule</id>
<content>
<module name="com.example.privatemodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.publicmodule",
"com.example.plugin.with.publicmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.publicmodule</id>
<content namespace="test-namespace">
<module name="com.example.publicmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.publicmodule",
"com.example.publicmodule/com.example.publicmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="<error descr="The 'com.example.privatemodule' module is private and declared in plugin 'com.example.plugin.with.privatemodule', so it cannot be accessed from module 'com.example.publicmodule' declared in plugin 'com.example.plugin.with.publicmodule'">com.example.privatemodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should report internal module with different namespace`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.internalmodule",
"com.example.plugin.with.internalmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.internalmodule</id>
<content namespace="test-other-namespace">
<module name="com.example.internalmodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.internalmodule",
"com.example.internalmodule/com.example.internalmodule.xml",
"""
<idea-plugin visibility="internal">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.currentmodule",
"com.example.plugin.with.currentmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.currentmodule</id>
<content namespace="test-namespace">
<module name="com.example.currentmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.currentmodule",
"com.example.currentmodule/com.example.currentmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="<error descr="The 'com.example.internalmodule' module is internal and declared in namespace 'test-other-namespace' in 'com.example.plugin.with.internalmodule/…/plugin.xml', so it cannot be accessed from module 'com.example.currentmodule', which is declared in namespace 'test-namespace' in 'com.example.plugin.with.currentmodule/…/plugin.xml'">com.example.internalmodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should report internal module without namespace`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.internalmodule",
"com.example.plugin.with.internalmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.internalmodule</id>
<content>
<module name="com.example.internalmodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.internalmodule",
"com.example.internalmodule/com.example.internalmodule.xml",
"""
<idea-plugin visibility="internal">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.currentmodule",
"com.example.plugin.with.currentmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.currentmodule</id>
<content namespace="test-namespace">
<module name="com.example.currentmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.currentmodule",
"com.example.currentmodule/com.example.currentmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="<error descr="The 'com.example.internalmodule' module is internal and declared without namespace in 'com.example.plugin.with.internalmodule/…/plugin.xml', so it cannot be accessed from module 'com.example.currentmodule', which is declared in namespace 'test-namespace' in 'com.example.plugin.with.currentmodule/…/plugin.xml'">com.example.internalmodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should report internal module when current module has no namespace`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.internalmodule",
"com.example.plugin.with.internalmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.internalmodule</id>
<content namespace="test-other-namespace">
<module name="com.example.internalmodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.internalmodule",
"com.example.internalmodule/com.example.internalmodule.xml",
"""
<idea-plugin visibility="internal">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.currentmodule",
"com.example.plugin.with.currentmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.currentmodule</id>
<content>
<module name="com.example.currentmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.currentmodule",
"com.example.currentmodule/com.example.currentmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="<error descr="The 'com.example.internalmodule' module is internal and declared in namespace 'test-other-namespace' in 'com.example.plugin.with.internalmodule/…/plugin.xml', so it cannot be accessed from module 'com.example.currentmodule', which is declared without namespace in 'com.example.plugin.with.currentmodule/…/plugin.xml'">com.example.internalmodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should report private module included via xi-include`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.privatemodule",
"com.example.plugin.with.privatemodule/META-INF/plugin.xml",
"""
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<id>com.example.plugin.with.privatemodule</id>
<xi:include href="/META-INF/privatemodules.xml"/>
</idea-plugin>
""".trimIndent())
addXmlFile(
"com.example.plugin.with.privatemodule/META-INF/privatemodules.xml",
"""
<idea-plugin>
<content>
<module name="com.example.privatemodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.publicmodule",
"com.example.plugin.with.publicmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.publicmodule</id>
<content>
<module name="com.example.publicmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.publicmodule",
"com.example.publicmodule/com.example.publicmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="<error descr="The 'com.example.privatemodule' module is private and declared in plugin 'com.example.plugin.with.privatemodule', so it cannot be accessed from module 'com.example.publicmodule' declared in plugin 'com.example.plugin.with.publicmodule'">com.example.privatemodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should report private module included via multiple xi-includes`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.privatemodule",
"com.example.plugin.with.privatemodule/META-INF/plugin.xml",
"""
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<id>com.example.plugin.with.privatemodule</id>
<xi:include href="/META-INF/modules.xml"/>
</idea-plugin>
""".trimIndent())
addXmlFile(
"com.example.plugin.with.privatemodule/META-INF/modules.xml",
"""
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="/META-INF/privatemodules.xml"/>
</idea-plugin>
""".trimIndent())
addXmlFile(
"com.example.plugin.with.privatemodule/META-INF/privatemodules.xml",
"""
<idea-plugin>
<content>
<module name="com.example.privatemodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.publicmodule",
"com.example.plugin.with.publicmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.publicmodule</id>
<content>
<module name="com.example.publicmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.publicmodule",
"com.example.publicmodule/com.example.publicmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="<error descr="The 'com.example.privatemodule' module is private and declared in plugin 'com.example.plugin.with.privatemodule', so it cannot be accessed from module 'com.example.publicmodule' declared in plugin 'com.example.plugin.with.publicmodule'">com.example.privatemodule</error>"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should not report public module`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.publicmodule",
"com.example.plugin.with.publicmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.publicmodule</id>
<content>
<module name="com.example.publicmodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.publicmodule",
"com.example.publicmodule/com.example.publicmodule.xml",
"""
<idea-plugin visibility="public">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.currentmodule",
"com.example.plugin.with.currentmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.currentmodule</id>
<content namespace="test-namespace">
<module name="com.example.currentmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.currentmodule",
"com.example.currentmodule/com.example.currentmodule.xml",
"""
<idea-plugin>
<dependencies>
<module name="com.example.publicmodule"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should not report internal module with same namespace`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.internalmodule",
"com.example.plugin.with.internalmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.internalmodule</id>
<content namespace="test-namespace">
<module name="com.example.internalmodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.internalmodule",
"com.example.internalmodule/com.example.internalmodule.xml",
"""
<idea-plugin visibility="internal">
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.plugin.with.currentmodule",
"com.example.plugin.with.currentmodule/META-INF/plugin.xml",
"""
<idea-plugin>
<id>com.example.plugin.with.currentmodule</id>
<content namespace="test-namespace">
<module name="com.example.currentmodule"/>
</content>
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.currentmodule",
"com.example.currentmodule/com.example.currentmodule.xml",
"""
<idea-plugin>
<dependencies>
<module name="com.example.internalmodule"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should not report private module in same plugin`() {
addModuleWithPluginDescriptor(
"com.example.plugin",
"com.example.plugin/META-INF/plugin.xml",
"""
<idea-plugin>
<content namespace="test-namespace">
<module name="com.example.privatemodule"/>
<module name="com.example.anothermodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.anothermodule",
"com.example.anothermodule/com.example.anothermodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="com.example.privatemodule"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should not report when both modules included in same plugin`() {
addModuleWithPluginDescriptor(
"com.example.plugin",
"com.example.plugin/META-INF/plugin.xml",
"""
<idea-plugin>
<content>
<module name="com.example.internalmodule"/>
<module name="com.example.currentmodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.internalmodule",
"com.example.internalmodule/com.example.internalmodule.xml",
"""
<idea-plugin visibility="internal">
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.currentmodule",
"com.example.currentmodule/com.example.currentmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="com.example.internalmodule"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should not report private module included via xi-include`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.privatemodule",
"com.example.plugin.with.privatemodule/META-INF/plugin.xml",
"""
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<id>com.example.plugin.with.privatemodule</id>
<content>
<module name="com.example.publicmodule"/>
</content>
<xi:include href="/META-INF/privatemodules.xml"/>
</idea-plugin>
""".trimIndent())
addXmlFile(
"com.example.plugin.with.privatemodule/META-INF/privatemodules.xml",
"""
<idea-plugin>
<content>
<module name="com.example.privatemodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.publicmodule",
"com.example.publicmodule/com.example.publicmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="com.example.privatemodule"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
fun `test should not report private module included via multiple xi-includes`() {
addModuleWithPluginDescriptor(
"com.example.plugin.with.privatemodule",
"com.example.plugin.with.privatemodule/META-INF/plugin.xml",
"""
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<id>com.example.plugin.with.privatemodule</id>
<content>
<module name="com.example.publicmodule"/>
</content>
<xi:include href="/META-INF/modules.xml"/>
</idea-plugin>
""".trimIndent())
addXmlFile(
"com.example.plugin.with.privatemodule/META-INF/modules.xml",
"""
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="/META-INF/privatemodules.xml"/>
</idea-plugin>
""".trimIndent())
addXmlFile(
"com.example.plugin.with.privatemodule/META-INF/privatemodules.xml",
"""
<idea-plugin>
<content>
<module name="com.example.privatemodule"/>
</content>
</idea-plugin>
""".trimIndent())
addModuleWithPluginDescriptor(
"com.example.privatemodule",
"com.example.privatemodule/com.example.privatemodule.xml",
"""
<idea-plugin visibility="private">
</idea-plugin>
""".trimIndent())
val testedFile = addModuleWithPluginDescriptor(
"com.example.publicmodule",
"com.example.publicmodule/com.example.publicmodule.xml",
"""
<idea-plugin visibility="public">
<dependencies>
<module name="com.example.privatemodule"/>
</dependencies>
</idea-plugin>
""".trimIndent())
testHighlighting(testedFile)
}
private fun addModuleWithPluginDescriptor(
moduleName: String,
pluginDescriptorFilePath: String,
@Language("XML") pluginDescriptorContent: String,
): PsiFile {
PsiTestUtil.addModule(project, PluginModuleType.getInstance(), moduleName, myFixture.tempDirFixture.findOrCreateDir(moduleName))
return addXmlFile(pluginDescriptorFilePath, pluginDescriptorContent)
}
private fun addXmlFile(relativePath: String, @Language("XML") fileText: String): PsiFile {
return myFixture.addFileToProject(relativePath, fileText)
}
private fun testHighlighting(testedFile: PsiFile) {
myFixture.testHighlighting(true, true, true, testedFile.virtualFile)
}
}
@@ -12,7 +12,7 @@ import java.util.stream.Stream;
public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase {
private static final int EXPECTED_INSPECTIONS_NUMBER = 85;
private static final int EXPECTED_INSPECTIONS_NUMBER = 86;
/**
* Inspections that are finished and intentionally disabled.
@@ -59,7 +59,6 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
List<String> allKnownDisabledInspections = new ArrayList<>(ContainerUtil.concat(DISABLED_INSPECTIONS, WIP_INSPECTIONS));
Collections.sort(allKnownDisabledInspections);
assertSameElements("Mismatch in known WIP inspections", disabledInspectionShortNames,
allKnownDisabledInspections);
assertSameElements("Mismatch in known WIP inspections", disabledInspectionShortNames, allKnownDisabledInspections);
}
}