From c321677ecb1d0cf718dbbd1a46e5289b5d334c53 Mon Sep 17 00:00:00 2001 From: Ilia Bogdanovich Date: Mon, 27 Jan 2025 15:28:15 +0100 Subject: [PATCH] KMT-520: Retain @Composable annotation when overriding Composable members. GitOrigin-RevId: c9f00e91e3c1f8a84a34effa091fe7bdd3c79e39 --- .../compose/intellij.compose.ide.plugin.iml | 2 + plugins/compose/resources/META-INF/plugin.xml | 3 +- ...poseOverrideImplementsAnnotationsFilter.kt | 21 ++++ .../intellij/compose/ide/plugin/IndexUtils.kt | 20 ++++ .../intellij.compose.ide.plugin.tests.iml | 1 + ...OverrideImplementsAnnotationsFilterTest.kt | 106 ++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 plugins/compose/src/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilter.kt create mode 100644 plugins/compose/src/com/intellij/compose/ide/plugin/IndexUtils.kt create mode 100644 plugins/compose/tests/test/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilterTest.kt diff --git a/plugins/compose/intellij.compose.ide.plugin.iml b/plugins/compose/intellij.compose.ide.plugin.iml index 81132ce2a0bd..b271ca4e03ec 100644 --- a/plugins/compose/intellij.compose.ide.plugin.iml +++ b/plugins/compose/intellij.compose.ide.plugin.iml @@ -14,5 +14,7 @@ + + \ No newline at end of file diff --git a/plugins/compose/resources/META-INF/plugin.xml b/plugins/compose/resources/META-INF/plugin.xml index b7e854182367..31bc8b1f6707 100644 --- a/plugins/compose/resources/META-INF/plugin.xml +++ b/plugins/compose/resources/META-INF/plugin.xml @@ -18,6 +18,7 @@ - + + diff --git a/plugins/compose/src/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilter.kt b/plugins/compose/src/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilter.kt new file mode 100644 index 000000000000..802d4f51e2e2 --- /dev/null +++ b/plugins/compose/src/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilter.kt @@ -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 { + return if (file is KtFile && isKotlinClassAvailable(file, COMPOSABLE_ANNOTATION_CLASS_ID)) { + arrayOf(COMPOSABLE_ANNOTATION_FQ_NAME.asString()) + } else { + arrayOf() + } + } +} diff --git a/plugins/compose/src/com/intellij/compose/ide/plugin/IndexUtils.kt b/plugins/compose/src/com/intellij/compose/ide/plugin/IndexUtils.kt new file mode 100644 index 000000000000..8d2ac4228cc5 --- /dev/null +++ b/plugins/compose/src/com/intellij/compose/ide/plugin/IndexUtils.kt @@ -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() +} diff --git a/plugins/compose/tests/intellij.compose.ide.plugin.tests.iml b/plugins/compose/tests/intellij.compose.ide.plugin.tests.iml index 60ea0cae2107..b9b6ba0fe7e9 100644 --- a/plugins/compose/tests/intellij.compose.ide.plugin.tests.iml +++ b/plugins/compose/tests/intellij.compose.ide.plugin.tests.iml @@ -14,6 +14,7 @@ + \ No newline at end of file diff --git a/plugins/compose/tests/test/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilterTest.kt b/plugins/compose/tests/test/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilterTest.kt new file mode 100644 index 000000000000..9eb87bbb477d --- /dev/null +++ b/plugins/compose/tests/test/com/intellij/compose/ide/plugin/ComposeOverrideImplementsAnnotationsFilterTest.kt @@ -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(), 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(), annotations.toList()) + } +}