Files
openide/plugins/devkit/intellij.kotlin.devkit/resources/inspectionDescriptions/CompanionObjectInExtension.html
Yann Cébron aad8b7218b [devkit] CompanionObjectInExtension description: improve
GitOrigin-RevId: cdf01b97a46ec7db84315afb3521f8f0ffd54e16
2025-02-12 18:26:00 +00:00

101 lines
3.8 KiB
HTML

<html>
<body>
Reports incorrect companion objects' usage in <a href="https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html">extensions</a>.
<p>
Kotlin companion object is always created once you try to load its containing class, and extension point implementations are supposed to be cheap to create.
Excessive classloading in plugins is a huge problem for IDE startup.
</p>
<p>Bad pattern:</p>
<pre><code lang="kotlin">
class KotlinDocumentationProvider : AbstractDocumentationProvider(), ExternalDocumentationProvider {
companion object {
private val LOG = Logger.getInstance(KotlinDocumentationProvider::class.java)
private val javaDocumentProvider = JavaDocumentationProvider()
}
}
</code></pre>
<p>
Here <code>KotlinDocumentationProvider</code> is an extension registered in <code>plugin.xml</code>:
</p>
<pre><code lang="xml">
&lt;lang.documentationProvider language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.KotlinDocumentationProvider"
order="first"/&gt;
</code></pre>
<p>
In this example <code>JavaDocumentationProvider</code> will be loaded from disk once someone just calls <code>new KotlinDocumentationProvider()</code>.
</p>
<p>
Kotlin companion objects in extension point implementation can only contain a logger and simple constants.
Other declarations may cause excessive classloading or early initialization of heavy resources (e.g., <code>TokenSet</code>, Regex, etc.)
when the extension class is loaded.
Note that even declarations marked with <code>@JvmStatic</code> still produce an extra class for the companion object, potentially causing expensive computations.
</p>
<p>
Instead of being stored in the companion object, these declarations must be top-level or stored in an object.
</p>
<h3>FAQ</h3>
<h4>How to rewrite run ConfigurationType?</h4>
<p>Move the declaration to top-level:</p>
<pre><code lang="kotlin">
// DO, use top level fun
internal fun mnRunConfigurationType(): MnRunConfigurationType = runConfigurationType&lt;MnRunConfigurationType&gt;()
internal class MnRunConfigurationType : ConfigurationType {
companion object { // DON'T
fun getInstance(): MnRunConfigurationType = runConfigurationType&lt;MnRunConfigurationType&gt;()
}
...
</code></pre>
<h4>How to rewrite FileType?</h4>
<p>Before:</p>
<pre><code lang="kotlin">
internal class SpringBootImportsFileType : LanguageFileType(SPILanguage.INSTANCE, true) {
companion object {
val FILE_TYPE = SpringBootImportsFileType()
...
</code></pre>
<p>After:</p>
<pre><code lang="kotlin">
internal object SpringBootImportsFileType : LanguageFileType(SPILanguage.INSTANCE, true) {
...
</code></pre>
<p>Use <code>INSTANCE</code> fieldName in <code>plugin.xml</code>:</p>
<pre><code lang="xml">
&lt;fileType name="Spring Boot Imports"
fieldName="INSTANCE"
implementationClass="com.intellij.spring.boot.spi.SpringBootImportsFileType"/&gt;
</code></pre>
<h4>How to rewrite CounterUsagesCollector?
</h4>
<h5>Internal API</h5>
<p>Before:</p>
<pre><code lang="kotlin">
class AntActionsUsagesCollector : CounterUsagesCollector() {
override fun getGroup(): EventLogGroup = GROUP
companion object {
private val GROUP = EventLogGroup("build.ant.actions", 1)
@JvmField
val runSelectedBuildAction = GROUP.registerEvent("RunSelectedBuild")
}
}
</code></pre>
<p>After:</p>
<pre><code lang="kotlin">
object AntActionsUsagesCollector : CounterUsagesCollector() {
override fun getGroup(): EventLogGroup = GROUP
private val GROUP = EventLogGroup("build.ant.actions", 1)
@JvmField
val runSelectedBuildAction = GROUP.registerEvent("RunSelectedBuild")
}
</code></pre>
<!-- tooltip end -->
<p><small>New in 2023.3</small>
</body>
</html>