KMT-520: Retain @Composable annotation when overriding Composable members.

GitOrigin-RevId: c9f00e91e3c1f8a84a34effa091fe7bdd3c79e39
This commit is contained in:
Ilia Bogdanovich
2025-01-30 13:38:23 +00:00
committed by intellij-monorepo-bot
parent 70f06e9a43
commit c321677ecb
6 changed files with 152 additions and 1 deletions
@@ -14,5 +14,7 @@
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="library" scope="PROVIDED" name="kotlinc.analysis-api" level="project" />
<orderEntry type="library" scope="PROVIDED" name="kotlinc.kotlin-compiler-common" level="project" />
<orderEntry type="module" module-name="intellij.platform.lang" />
<orderEntry type="module" module-name="kotlin.base.indices" />
</component>
</module>
@@ -18,6 +18,7 @@
</extensions>
<extensions defaultExtensionNs="com.intellij">
<lang.inspectionSuppressor language="kotlin" implementationClass="com.intellij.compose.ide.plugin.ComposableNamingInspectionSuppressor"/>
<lang.inspectionSuppressor language="kotlin" implementationClass="com.intellij.compose.ide.plugin.ComposableNamingInspectionSuppressor" />
<overrideImplementsAnnotationsFilter id="ComposeOverrideImplementsAnnotationsFilter" implementation="com.intellij.compose.ide.plugin.ComposeOverrideImplementsAnnotationsFilter" />
</extensions>
</idea-plugin>
@@ -0,0 +1,21 @@
package com.intellij.compose.ide.plugin
import com.intellij.codeInsight.generation.OverrideImplementsAnnotationsFilter
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtFile
/**
* Extension for [OverrideImplementsAnnotationsFilter], which checks if the "Composable" annotation is on the classpath,
* and if that's the case - retains it while doing overrides.
* This is more generic than the Android's `com.android.tools.compose.ComposeOverrideImplementsAnnotationsFilter` that only checks
* module's `usesCompose` flag, which only works for Android modules, but not for multiplatform.
*/
internal class ComposeOverrideImplementsAnnotationsFilter : OverrideImplementsAnnotationsFilter {
override fun getAnnotations(file: PsiFile): Array<String> {
return if (file is KtFile && isKotlinClassAvailable(file, COMPOSABLE_ANNOTATION_CLASS_ID)) {
arrayOf(COMPOSABLE_ANNOTATION_FQ_NAME.asString())
} else {
arrayOf()
}
}
}
@@ -0,0 +1,20 @@
package com.intellij.compose.ide.plugin
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
import org.jetbrains.kotlin.name.ClassId
/**
* Checks if the given Kotlin class is accessible from the specified call site.
*
* @param callSite The PSI file representing the call site.
* @param classId The identifier of the class to check accessibility for.
* @return `true` if the class is accessible, `false` otherwise.
*/
internal fun isKotlinClassAvailable(callSite: PsiFile, classId: ClassId): Boolean {
val module = ModuleUtilCore.findModuleForPsiElement(callSite) ?: return false
val moduleScope = module.getModuleWithDependenciesAndLibrariesScope(/*includeTests = */true)
val foundClasses = KotlinFullClassNameIndex[classId.asFqNameString(), module.project, moduleScope]
return foundClasses.isNotEmpty()
}
@@ -14,6 +14,7 @@
<orderEntry type="module" module-name="kotlin.base.plugin" scope="TEST" />
<orderEntry type="module" module-name="kotlin.test-framework" scope="TEST" />
<orderEntry type="library" scope="TEST" name="kotlinc.analysis-api" level="project" />
<orderEntry type="library" scope="TEST" name="kotlinc.kotlin-compiler-common" level="project" />
</component>
<component name="TestModuleProperties" production-module="intellij.compose.ide.plugin" />
</module>
@@ -0,0 +1,106 @@
package com.intellij.compose.ide.plugin
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
class ComposeOverrideImplementsAnnotationsFilterTest : KotlinLightCodeInsightFixtureTestCase() {
private val filter = ComposeOverrideImplementsAnnotationsFilter()
fun testKotlinFileWithComposeDependency() {
// Prepare
myFixture.addFileToProject(
"Composable.kt",
"""
package androidx.compose.runtime
annotation class Composable
""".trimIndent()
)
val file = myFixture.addFileToProject(
"MyFile.kt",
"""
package test
import androidx.compose.runtime.Composable
interface Base {
@Composable
fun view()
}
class BaseImpl : Base
""".trimIndent()
)
// Do
val annotations = filter.getAnnotations(file)
// Check
assertEquals(listOf(COMPOSABLE_ANNOTATION_FQ_NAME.asString()), annotations.toList())
}
fun testKotlinFileWithoutComposeDependency() {
// Prepare
val file = myFixture.addFileToProject(
"MyFile.kt",
"""
package test
import androidx.compose.runtime.Composable
interface Base {
@Composable
fun view()
}
class BaseImpl : Base {}
""".trimIndent()
)
// Do
val annotations = filter.getAnnotations(file)
// Check
assertEquals(emptyList<String>(), annotations.toList())
}
fun testJavaFile() {
// Prepare
myFixture.addFileToProject(
"Composable.kt",
"""
package androidx.compose.runtime
annotation class Composable
""".trimIndent()
)
myFixture.addFileToProject(
"Base.kt",
"""
package test
import androidx.compose.runtime.Composable
interface Base {
@Composable
fun view()
}
""".trimIndent()
)
val file = myFixture.addFileToProject(
"BaseImpl.java",
"""
package test;
import androidx.compose.runtime.Composable;
class BaseImpl implements Base {}
""".trimIndent()
)
// Do
val annotations = filter.getAnnotations(file)
// Check
assertEquals(emptyList<String>(), annotations.toList())
}
}