Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify compatibility with K2 mode for Kotlin-dependent plugins #1150

Merged
merged 16 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ package com.intellij.featureExtractor

import com.jetbrains.plugin.structure.base.plugin.PluginIcon
import com.jetbrains.plugin.structure.base.plugin.ThirdPartyDependency
import com.jetbrains.plugin.structure.intellij.plugin.*
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import com.jetbrains.plugin.structure.intellij.plugin.IdePluginContentDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.IdeTheme
import com.jetbrains.plugin.structure.intellij.plugin.KotlinPluginMode
import com.jetbrains.plugin.structure.intellij.plugin.ModuleDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.MutableIdePluginContentDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.OptionalPluginDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.PluginDependency
import com.jetbrains.plugin.structure.intellij.plugin.ProductDescriptor
import com.jetbrains.plugin.structure.intellij.version.IdeVersion
import org.jdom2.Document
import org.jdom2.Element
Expand Down Expand Up @@ -37,6 +45,7 @@ data class MockIdePlugin(
override val useIdeClassLoader = false
override val isImplementationDetail = false
override val isV2: Boolean = false
override val kotlinPluginMode: KotlinPluginMode = KotlinPluginMode.Implicit
override val hasDotNetPart: Boolean = false
override val declaredThemes = emptyList<IdeTheme>()
override val thirdPartyDependencies: List<ThirdPartyDependency> = emptyList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ interface IdePlugin : Plugin {

val isV2: Boolean

val kotlinPluginMode: KotlinPluginMode

val hasDotNetPart: Boolean

fun isCompatibleWithIde(ideVersion: IdeVersion): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.jetbrains.plugin.structure.intellij.plugin
import com.jetbrains.plugin.structure.base.plugin.PluginIcon
import com.jetbrains.plugin.structure.base.plugin.ThirdPartyDependency
import com.jetbrains.plugin.structure.base.problems.PluginProblem
import com.jetbrains.plugin.structure.intellij.plugin.KotlinPluginMode.Implicit
import com.jetbrains.plugin.structure.intellij.version.IdeVersion
import org.jdom2.Document
import org.jdom2.Element
Expand Down Expand Up @@ -45,6 +46,8 @@ class IdePluginImpl : IdePlugin, StructurallyValidated {

override var isV2: Boolean = false

override var kotlinPluginMode: KotlinPluginMode = Implicit

override var hasDotNetPart: Boolean = false

override var underlyingDocument: Document = Document()
Expand Down Expand Up @@ -112,6 +115,7 @@ class IdePluginImpl : IdePlugin, StructurallyValidated {
useIdeClassLoader = old.useIdeClassLoader
isImplementationDetail = old.isImplementationDetail
isV2 = old.isV2
kotlinPluginMode = old.kotlinPluginMode
hasDotNetPart = old.hasDotNetPart
underlyingDocument = old.underlyingDocument
declaredThemes.addAll(old.declaredThemes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jetbrains.plugin.structure.intellij.plugin

import com.jetbrains.plugin.structure.base.plugin.PluginIcon
import com.jetbrains.plugin.structure.base.plugin.ThirdPartyDependency
import com.jetbrains.plugin.structure.intellij.plugin.KotlinPluginMode.Implicit
import com.jetbrains.plugin.structure.intellij.version.IdeVersion
import org.jdom2.Document
import org.jdom2.Element
Expand Down Expand Up @@ -42,6 +43,7 @@ class InvalidPlugin(override val underlyingDocument: Document) : IdePlugin {
override val useIdeClassLoader: Boolean = false
override val isImplementationDetail: Boolean = false
override val isV2: Boolean = false
override val kotlinPluginMode: KotlinPluginMode = Implicit
override fun isCompatibleWithIde(ideVersion: IdeVersion): Boolean = false
override val hasDotNetPart: Boolean = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.jetbrains.plugin.structure.intellij.plugin

sealed class KotlinPluginMode(
@Suppress("unused") val isK1Compatible: Boolean,
@Suppress("unused") val isK2Compatible: Boolean
) {
object K1AndK2Compatible : KotlinPluginMode(isK1Compatible = true, isK2Compatible = true) {
override fun toString() = "K1 and K2 compatible"
}

object K2OnlyCompatible : KotlinPluginMode(isK1Compatible = false, isK2Compatible = true) {
override fun toString() = "K2-only compatible"
}

object K1OnlyCompatible : KotlinPluginMode(isK1Compatible = true, isK2Compatible = false) {
override fun toString() = "K1-only compatible"
}

object Implicit : KotlinPluginMode(isK1Compatible = true, isK2Compatible = false) {
override fun toString() = "Implicit compatibility mode (K1-only)"
}

object Invalid : KotlinPluginMode(isK1Compatible = false, isK2Compatible = false) {
override fun toString() = "Illegal compatibility mode (neither K1 nor K2 compatbile)"
}

companion object {
fun parse(isK1Compatible: Boolean, isK2Compatible: Boolean): KotlinPluginMode =
if (isK1Compatible) {
if (isK2Compatible) K1AndK2Compatible else K1OnlyCompatible
} else {
if (isK2Compatible) K2OnlyCompatible else Invalid
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.jetbrains.plugin.structure.intellij.beans.ProductDescriptorBean
import com.jetbrains.plugin.structure.intellij.extractor.PluginBeanExtractor
import com.jetbrains.plugin.structure.intellij.problems.*
import com.jetbrains.plugin.structure.intellij.resources.ResourceResolver
import com.jetbrains.plugin.structure.intellij.verifiers.K2IdeModeCompatibilityVerifier
import com.jetbrains.plugin.structure.intellij.verifiers.LanguageBundleExtensionPointVerifier
import com.jetbrains.plugin.structure.intellij.verifiers.PluginIdVerifier
import com.jetbrains.plugin.structure.intellij.verifiers.PluginUntilBuildVerifier
Expand Down Expand Up @@ -379,12 +380,20 @@ internal class PluginCreator private constructor(
"com.intellij.applicationService" -> idePlugin.appContainerDescriptor.services += readServiceDescriptor(extensionElement, epName)
"com.intellij.projectService" -> idePlugin.projectContainerDescriptor.services += readServiceDescriptor(extensionElement, epName)
"com.intellij.moduleService" -> idePlugin.moduleContainerDescriptor.services += readServiceDescriptor(extensionElement, epName)
else -> idePlugin.extensions.getOrPut(epName) { arrayListOf() }.add(extensionElement)
"org.jetbrains.kotlin.supportsKotlinPluginMode" -> {
idePlugin.addExtension(epName, extensionElement)
idePlugin.kotlinPluginMode = readKotlinPluginMode(extensionElement)
}
else -> idePlugin.addExtension(epName, extensionElement)
}
}
}
}

private fun IdePluginImpl.addExtension(epName: String, extensionElement: Element) {
extensions.getOrPut(epName) { arrayListOf() }.add(extensionElement)
}

private fun readExtensionPoints(rootElement: Element, idePlugin: IdePluginImpl) {
for (extensionPointsRoot in rootElement.getChildren("extensionPoints")) {
for (extensionPoint in extensionPointsRoot.children) {
Expand Down Expand Up @@ -522,6 +531,12 @@ internal class PluginCreator private constructor(
}
}

private fun readKotlinPluginMode(extensionElement: Element): KotlinPluginMode {
val supportsK1 = extensionElement.getAttributeBooleanValue("supportsK1", true)
val supportsK2 = extensionElement.getAttributeBooleanValue("supportsK2", false)
return KotlinPluginMode.parse(supportsK1, supportsK2)
}

private fun validatePluginBean(bean: PluginBean, validateDescriptor: Boolean) {
if (validateDescriptor || bean.url != null) {
validateBeanUrl(bean.url)
Expand Down Expand Up @@ -673,6 +688,7 @@ internal class PluginCreator private constructor(
ServiceExtensionPointPreloadVerifier().verify(plugin, ::registerProblem)
StatusBarWidgetFactoryExtensionPointVerifier().verify(plugin, ::registerProblem)
LanguageBundleExtensionPointVerifier().verify(plugin, ::registerProblem)
K2IdeModeCompatibilityVerifier().verify(plugin, ::registerProblem, descriptorPath)
}

private fun resolveDocumentAndValidateBean(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.jetbrains.plugin.structure.intellij.beans.ModuleBean
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import com.jetbrains.plugin.structure.intellij.plugin.IdePluginContentDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.IdeTheme
import com.jetbrains.plugin.structure.intellij.plugin.KotlinPluginMode
import com.jetbrains.plugin.structure.intellij.plugin.ModuleDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.MutableIdePluginContentDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.OptionalPluginDescriptor
Expand Down Expand Up @@ -39,6 +40,7 @@ class IdeModule(override val pluginId: String) : IdePlugin {
override val productDescriptor = null
override val useIdeClassLoader = false
override val isV2 = true
override val kotlinPluginMode: KotlinPluginMode = KotlinPluginMode.Implicit
override val url = null
override val changeNotes = null
override val description = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ class InvalidDependencyId(descriptorPath: String, invalidPluginId: String) : Inv
get() = Level.ERROR
}

/**
* @see [com.jetbrains.plugin.structure.intellij.verifiers.K2IdeModeCompatibilityVerifier]
*/
private const val INVALID_KOTLIN_PLUGIN_MODE_MESSAGE = "Plugin depends on the Kotlin plugin (org.jetbrains.kotlin)," +
"but does not declare compatibility with either " +
"K1 Mode or K2 mode in the <org.jetbrains.kotlin.supportsKotlinPluginMode> extension. Please ensure that the " +
"'supportsK1' or 'supportsK2' parameter (or both) is set to 'true'. " +
"This feature is available for IntelliJ IDEA 2024.2.1 or later."

class InvalidKotlinPluginMode(descriptorPath: String) : InvalidDescriptorProblem(
descriptorPath = descriptorPath,
detailedMessage = INVALID_KOTLIN_PLUGIN_MODE_MESSAGE
) {
override val level
get() = Level.ERROR

override val hint = ProblemSolutionHint(
"""<supportsKotlinPluginMode supportsK1="false" supportsK2="false" />""",
"https://kotlin.github.io/analysis-api/migrating-from-k1.html#declaring-compatibility-with-the-k2-kotlin-mode"
)
}

class InvalidModuleBean(descriptorPath: String) : InvalidDescriptorProblem(
descriptorPath = descriptorPath,
detailedMessage = "The <module value> parameter is empty. It must be specified as <module value=\"my.module\"/>."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,26 @@ class UnknownServiceClientValue(descriptorPath: String, serviceClient: String) :
) {
override val level
get() = Level.WARNING
}

/**
* @see [K2IdeModeCompatibilityVerifier]
*/
private const val UNDECLARED_KOTLIN_K2_COMPATIBILITY_MODE_MESSAGE = "Plugin depends on the Kotlin plugin (org.jetbrains.kotlin) but does not declare " +
"a compatibility mode in the <org.jetbrains.kotlin.supportsKotlinPluginMode> extension. " +
"This feature is available for IntelliJ IDEA 2024.2.1 or later."

data class UndeclaredKotlinK2CompatibilityMode(val descriptorPath: String) : InvalidDescriptorProblem(
descriptorPath = descriptorPath,
detailedMessage = UNDECLARED_KOTLIN_K2_COMPATIBILITY_MODE_MESSAGE
) {
val detailedMessage = UNDECLARED_KOTLIN_K2_COMPATIBILITY_MODE_MESSAGE

override val level
get() = Level.WARNING

override val hint = ProblemSolutionHint(
YannCebron marked this conversation as resolved.
Show resolved Hide resolved
"<supportsKotlinPluginMode supportsK2=\"true\" />",
"https://kotlin.github.io/analysis-api/migrating-from-k1.html#declaring-compatibility-with-the-k2-kotlin-mode"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class IntelliJPluginCreationResultResolver : PluginCreationResultResolver {
PropertyWithDefaultValue::class,
InvalidDependencyId::class,
InvalidModuleBean::class,
InvalidKotlinPluginMode::class,
SinceBuildNotSpecified::class,
InvalidSinceBuild::class,
InvalidUntilBuild::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ package com.jetbrains.plugin.structure.intellij.verifiers
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import com.jetbrains.plugin.structure.intellij.plugin.IdePluginContentDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.IdePluginContentDescriptor.ServiceDescriptor
import com.jetbrains.plugin.structure.intellij.plugin.KotlinPluginMode
import com.jetbrains.plugin.structure.intellij.plugin.KotlinPluginMode.Implicit
import com.jetbrains.plugin.structure.intellij.plugin.PluginVendors.isDevelopedByJetBrains
import com.jetbrains.plugin.structure.intellij.problems.InvalidKotlinPluginMode
import com.jetbrains.plugin.structure.intellij.problems.LanguageBundleExtensionPointIsInternal
import com.jetbrains.plugin.structure.intellij.problems.ServiceExtensionPointPreloadNotSupported
import com.jetbrains.plugin.structure.intellij.problems.StatusBarWidgetFactoryExtensionPointIdMissing
import com.jetbrains.plugin.structure.intellij.problems.UndeclaredKotlinK2CompatibilityMode

private const val KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin"

/**
* Rule: Service Extension Point preloading is deprecated.
Expand Down Expand Up @@ -64,4 +70,24 @@ class LanguageBundleExtensionPointVerifier {
}
}
}
}

/**
* Rule: When depending on the Kotlin plugin, an EP
* `org.jetbrains.kotlin.supportsKotlinPluginMode` must be declared with corresponding compatibility attributes.
* See [Kotlin Analysis API](https://kotlin.github.io/analysis-api/migrating-from-k1.html) documentation.
* Applicable for IntelliJ IDEA 2024.2.1 and higher.
*
*/
class K2IdeModeCompatibilityVerifier {
fun verify(plugin: IdePlugin, problemRegistrar: ProblemRegistrar, descriptorPath: String) {
val hasKotlinDependency = plugin.dependencies.any { it.id == KOTLIN_PLUGIN_ID }
if (hasKotlinDependency) {
when(plugin.kotlinPluginMode) {
Implicit -> problemRegistrar.registerProblem(UndeclaredKotlinK2CompatibilityMode(descriptorPath))
KotlinPluginMode.Invalid -> problemRegistrar.registerProblem(InvalidKotlinPluginMode(descriptorPath))
else -> Unit
}
}
}
}
Loading
Loading