diff --git a/CHANGELOG.md b/CHANGELOG.md index a58cd40..6aa1e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Kobweb IntelliJ Plugin +## [0.1.4] + +### Added + +- Support for Kotlin K2 mode +- Updated the compatibility range to 2024.2.1 - 2024.3 + ## [0.1.3] ### Added diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index f5e62bf..bf68226 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -26,10 +26,8 @@ dependencies { intellijPlatform { pluginModule(implementation(project(":kobweb-model"))) // Interesting statistics: https://plugins.jetbrains.com/docs/marketplace/product-versions-in-use-statistics.html - // We target 2023.3 for: - // - ProjectActivity (available since 2023.1) - // - Kotlin 1.9 support - intellijIdeaCommunity("2023.3") + // We target 2024.2.1 as it is the earliest version supporting K2 mode / the Analysis API + intellijIdeaCommunity("2024.2.1") bundledPlugins( "org.jetbrains.kotlin", @@ -72,7 +70,7 @@ intellijPlatform { ideaVersion { //sinceBuild derived from intellij.version - untilBuild = "242.*" // Include EAP + untilBuild = "243.*" // Include EAP } changeNotes = provider { @@ -149,7 +147,7 @@ changelog { fun Project.isSnapshot() = version.toString().endsWith("-SNAPSHOT") -val jvmTarget = JvmTarget.JVM_17 +val jvmTarget = JvmTarget.JVM_21 tasks.withType().configureEach { sourceCompatibility = jvmTarget.target targetCompatibility = jvmTarget.target diff --git a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/colors/KobwebColorProvider.kt b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/colors/KobwebColorProvider.kt index 432b60f..a5c3489 100644 --- a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/colors/KobwebColorProvider.kt +++ b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/colors/KobwebColorProvider.kt @@ -8,14 +8,17 @@ import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement import com.varabyte.kobweb.intellij.util.kobweb.isInKobwebSource import com.varabyte.kobweb.intellij.util.kobweb.isInReadableKobwebProject -import org.jetbrains.kotlin.idea.base.psi.kotlinFqName -import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.base.KaConstantValue +import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull +import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.js.translate.declaration.hasCustomGetter import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import java.awt.Color import kotlin.math.abs import kotlin.math.roundToInt @@ -39,8 +42,6 @@ import kotlin.math.roundToInt */ private const val MAX_SEARCH_DEPTH = 15 -private const val KOBWEB_COLOR_COMPANION_FQ_NAME = "com.varabyte.kobweb.compose.ui.graphics.Color.Companion" - /** * Enables showing small rectangular gutter icons that preview Kobweb colors */ @@ -105,142 +106,115 @@ private fun Float.toColorInt(): Int { return (this * 255f).roundToInt().coerceIn(0, 255) } +private object ColorFunctions { + private val KOBWEB_COLOR_COMPANION_ID = + ClassId.fromString("com/varabyte/kobweb/compose/ui/graphics/Color.Companion") + val rgb = CallableId(KOBWEB_COLOR_COMPANION_ID, Name.identifier("rgb")) + val rgba = CallableId(KOBWEB_COLOR_COMPANION_ID, Name.identifier("rgba")) + val argb = CallableId(KOBWEB_COLOR_COMPANION_ID, Name.identifier("argb")) + val hsl = CallableId(KOBWEB_COLOR_COMPANION_ID, Name.identifier("hsl")) + val hsla = CallableId(KOBWEB_COLOR_COMPANION_ID, Name.identifier("hsla")) + + val entries = listOf(rgb, rgba, argb, hsl, hsla) + val names = entries.map { it.callableName } +} + +private fun KaConstantValue.asIntOrNull(): Int? = (this as? KaConstantValue.IntValue)?.value +private fun KaConstantValue.asFloatOrNull(): Float? = (this as? KaConstantValue.FloatValue)?.value +private fun KaConstantValue.asLongOrNull(): Long? = (this as? KaConstantValue.LongValue)?.value + /** * Checks if a call expression represents a Kobweb color function call and if so, try extracting the color from it. * * @return The specified color, if it could be parsed and the callee is a Kobweb color function, otherwise null */ private fun KtCallExpression.tryParseKobwebColorFunctionColor(): Color? { - "$KOBWEB_COLOR_COMPANION_FQ_NAME.rgb".let { rgbFqn -> - this.extractConstantArguments1(rgbFqn)?.let { (rgb) -> - return tryCreateRgbColor(rgb) - } - - this.extractConstantArguments1(rgbFqn)?.let { (rgb) -> - return tryCreateRgbColor(rgb.toInt()) - } - - this.extractConstantArguments3(rgbFqn)?.let { (r, g, b) -> - return tryCreateRgbColor(r, g, b) - } - - this.extractConstantArguments3(rgbFqn)?.let { (r, g, b) -> - return tryCreateRgbColor(r.toColorInt(), g.toColorInt(), b.toColorInt()) + val ktExpression = (this.calleeExpression as? KtNameReferenceExpression) + ?.takeIf { it.getReferencedNameAsName() in ColorFunctions.names } + ?: return null + analyze(ktExpression) { + val callableId = (ktExpression.mainReference.resolveToSymbol() as? KaFunctionSymbol) + ?.callableId + ?.takeIf { it in ColorFunctions.entries } + ?: return@analyze + val functionArgs = ktExpression.resolveToCall()?.successfulFunctionCallOrNull()?.argumentMapping + ?: return@analyze + + when (callableId) { + ColorFunctions.rgb -> when (functionArgs.size) { + 1 -> { + val constantValue = functionArgs.entries.single().key.evaluate() + val rgb = constantValue?.asIntOrNull() ?: constantValue?.asLongOrNull()?.toInt() ?: return@analyze + return tryCreateRgbColor(rgb) + } + + 3 -> { + val (r, g, b) = functionArgs.mapNotNull { + val constantValue = it.key.evaluate() + constantValue?.asIntOrNull() ?: constantValue?.asFloatOrNull()?.toColorInt() + }.takeIf { it.size == 3 } ?: return@analyze + return tryCreateRgbColor(r, g, b) + } + } + + ColorFunctions.rgba -> when (functionArgs.size) { + 1 -> { + val constantValue = functionArgs.entries.single().key.evaluate() + val rgb = constantValue?.asIntOrNull() ?: constantValue?.asLongOrNull()?.toInt() ?: return@analyze + return tryCreateRgbColor(rgb shr 8) + } + + 4 -> { + val (r, g, b) = functionArgs.entries.take(3).mapNotNull { + val constantValue = it.key.evaluate() + constantValue?.asIntOrNull() ?: constantValue?.asFloatOrNull()?.toColorInt() + }.takeIf { it.size == 3 } ?: return@analyze + return tryCreateRgbColor(r, g, b) + } + } + + ColorFunctions.argb -> when (functionArgs.size) { + 1 -> { + val constantValue = functionArgs.entries.single().key.evaluate() + val rgb = constantValue?.asIntOrNull() ?: constantValue?.asLongOrNull()?.toInt() ?: return@analyze + return tryCreateRgbColor(rgb and 0x00_FF_FF_FF) + } + + 4 -> { + val (r, g, b) = functionArgs.entries.drop(1).mapNotNull { + val constantValue = it.key.evaluate() + constantValue?.asIntOrNull() ?: constantValue?.asFloatOrNull()?.toColorInt() + }.takeIf { it.size == 3 } ?: return@analyze + return tryCreateRgbColor(r, g, b) + } + } + + ColorFunctions.hsl -> { + val h = functionArgs.entries.first().key.evaluate() + .let { it?.asIntOrNull() ?: it?.asFloatOrNull()?.roundToInt() } + ?: return@analyze + val (s, l) = functionArgs.entries.drop(1) + .mapNotNull { it.key.evaluate()?.asFloatOrNull() } + .takeIf { it.size == 2 } + ?: return@analyze + return tryCreateHslColor(h, s, l) + } + + ColorFunctions.hsla -> { + val h = functionArgs.entries.first().key.evaluate() + .let { it?.asIntOrNull() ?: it?.asFloatOrNull()?.roundToInt() } + ?: return@analyze + val (s, l) = functionArgs.entries.drop(1).dropLast(1) + .mapNotNull { it.key.evaluate()?.asFloatOrNull() } + .takeIf { it.size == 2 } + ?: return@analyze + return tryCreateHslColor(h, s, l) + } } } - - "$KOBWEB_COLOR_COMPANION_FQ_NAME.rgba".let { rgbaFqn -> - this.extractConstantArguments1(rgbaFqn)?.let { (rgb) -> - return tryCreateRgbColor(rgb shr 8) - } - - this.extractConstantArguments1(rgbaFqn)?.let { (rgb) -> - return tryCreateRgbColor(rgb.toInt() shr 8) - } - - this.extractConstantArguments4(rgbaFqn)?.let { (r, g, b) -> - return tryCreateRgbColor(r, g, b) - } - - this.extractConstantArguments4(rgbaFqn)?.let { (r, g, b) -> - return tryCreateRgbColor(r, g, b) - } - - this.extractConstantArguments4(rgbaFqn)?.let { (r, g, b) -> - return tryCreateRgbColor(r.toColorInt(), g.toColorInt(), b.toColorInt()) - } - } - - "$KOBWEB_COLOR_COMPANION_FQ_NAME.argb".let { argbFqn -> - this.extractConstantArguments1(argbFqn)?.let { (rgb) -> - return tryCreateRgbColor(rgb and 0x00_FF_FF_FF) - } - - this.extractConstantArguments1(argbFqn)?.let { (rgb) -> - return tryCreateRgbColor(rgb.toInt() and 0x00_FF_FF_FF) - } - - this.extractConstantArguments4(argbFqn)?.let { (_, r, g, b) -> - return tryCreateRgbColor(r, g, b) - } - - this.extractConstantArguments4(argbFqn)?.let { (_, r, g, b) -> - return tryCreateRgbColor(r, g, b) - } - - this.extractConstantArguments4(argbFqn)?.let { (_, r, g, b) -> - return tryCreateRgbColor(r.toColorInt(), g.toColorInt(), b.toColorInt()) - } - } - - "$KOBWEB_COLOR_COMPANION_FQ_NAME.hsl".let { hslFqn -> - this.extractConstantArguments3(hslFqn)?.let { (h, s, l) -> - return tryCreateHslColor(h, s, l) - } - - this.extractConstantArguments3(hslFqn)?.let { (h, s, l) -> - return tryCreateHslColor(h.roundToInt(), s, l) - } - } - - "$KOBWEB_COLOR_COMPANION_FQ_NAME.hsla".let { hslaFqn -> - this.extractConstantArguments4(hslaFqn)?.let { (h, s, l) -> - return tryCreateHslColor(h, s, l) - } - - this.extractConstantArguments4(hslaFqn)?.let { (h, s, l) -> - return tryCreateHslColor(h.roundToInt(), s, l) - } - } - return null } -private data class Values( - val v1: T1, - val v2: T2, - val v3: T3, - val v4: T4 -) - -private inline fun KtValueArgument.extractConstantValue(): T? { - val constantExpression = getArgumentExpression() as? KtConstantExpression ?: return null - val bindingContext = constantExpression.analyze(BodyResolveMode.PARTIAL) - val constant = bindingContext.get(BindingContext.COMPILE_TIME_VALUE, constantExpression) ?: return null - val type = bindingContext.getType(constantExpression) ?: return null - return constant.getValue(type) as? T -} - -private fun KtCallExpression.valueArgumentsIf(fqn: String, requiredSize: Int): List? { - val calleeExpression = calleeExpression as? KtNameReferenceExpression ?: return null - val callee = calleeExpression.findDeclaration() as? KtNamedFunction ?: return null - if (callee.kotlinFqName?.asString() != fqn) return null - return valueArguments.takeIf { it.size == requiredSize } -} - -private inline fun KtCallExpression.extractConstantArguments1(fqn: String): Values? { - val valueArguments = valueArgumentsIf(fqn, 1) ?: return null - val v1: I? = valueArguments[0].extractConstantValue() - return if (v1 != null) Values(v1, Unit, Unit, Unit) else null -} - -private inline fun KtCallExpression.extractConstantArguments3(fqn: String): Values? { - val valueArguments = valueArgumentsIf(fqn, 3) ?: return null - val v1: I1? = valueArguments[0].extractConstantValue() - val v2: I2? = valueArguments[1].extractConstantValue() - val v3: I3? = valueArguments[2].extractConstantValue() - return if (v1 != null && v2 != null && v3 != null) Values(v1, v2, v3, Unit) else null -} - -private inline fun KtCallExpression.extractConstantArguments4(fqn: String): Values? { - val valueArguments = valueArgumentsIf(fqn, 4) ?: return null - val v1: I1? = valueArguments[0].extractConstantValue() - val v2: I2? = valueArguments[1].extractConstantValue() - val v3: I3? = valueArguments[2].extractConstantValue() - val v4: I4? = valueArguments[3].extractConstantValue() - return if (v1 != null && v2 != null && v3 != null && v4 != null) Values(v1, v2, v3, v4) else null -} - private fun tryCreateRgbColor(r: Int, g: Int, b: Int) = runCatching { Color(r, g, b) }.getOrNull() diff --git a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/FunctionNameInspectionSuppressor.kt b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/FunctionNameInspectionSuppressor.kt index 61159ff..c6ad74a 100644 --- a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/FunctionNameInspectionSuppressor.kt +++ b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/FunctionNameInspectionSuppressor.kt @@ -8,9 +8,11 @@ import com.intellij.psi.util.CachedValue import com.varabyte.kobweb.intellij.util.kobweb.isInKobwebSource import com.varabyte.kobweb.intellij.util.kobweb.isInReadableKobwebProject import com.varabyte.kobweb.intellij.util.psi.hasAnyAnnotation +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.psi.KtNamedFunction private val IS_COMPOSABLE_KEY = Key>("IS_COMPOSABLE") +private val COMPOSABLE_ANNOTATION_ID = ClassId.fromString("androidx/compose/runtime/Composable") /** * Suppress the "Function name should start with a lowercase letter" inspection. @@ -21,7 +23,7 @@ class FunctionNameInspectionSuppressor : InspectionSuppressor { if (!element.isInReadableKobwebProject() && !element.isInKobwebSource()) return false val ktFunction = element.parent as? KtNamedFunction ?: return false - return ktFunction.hasAnyAnnotation(IS_COMPOSABLE_KEY, "androidx.compose.runtime.Composable") + return ktFunction.hasAnyAnnotation(IS_COMPOSABLE_KEY, COMPOSABLE_ANNOTATION_ID) } override fun getSuppressActions(element: PsiElement?, toolId: String) = emptyArray() diff --git a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/UnusedInspectionSuppressor.kt b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/UnusedInspectionSuppressor.kt index 58028b8..d8af4f6 100644 --- a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/UnusedInspectionSuppressor.kt +++ b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/inspections/UnusedInspectionSuppressor.kt @@ -7,17 +7,18 @@ import com.intellij.psi.PsiElement import com.intellij.psi.util.CachedValue import com.varabyte.kobweb.intellij.util.kobweb.isInReadableKobwebProject import com.varabyte.kobweb.intellij.util.psi.hasAnyAnnotation +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.psi.KtNamedFunction private val ANNOTATION_GENERATES_CODE_KEY = Key>("ANNOTATION_GENERATES_CODE") private val SUPPRESS_UNUSED_WHEN_ANNOTATED_WITH = arrayOf( - "com.varabyte.kobweb.api.Api", - "com.varabyte.kobweb.api.init.InitApi", - "com.varabyte.kobweb.core.App", - "com.varabyte.kobweb.core.Page", - "com.varabyte.kobweb.core.init.InitKobweb", - "com.varabyte.kobweb.silk.init.InitSilk", + ClassId.fromString("com/varabyte/kobweb/api/Api"), + ClassId.fromString("com/varabyte/kobweb/api/init/InitApi"), + ClassId.fromString("com/varabyte/kobweb/core/App"), + ClassId.fromString("com/varabyte/kobweb/core/Page"), + ClassId.fromString("com/varabyte/kobweb/core/init/InitKobweb"), + ClassId.fromString("com/varabyte/kobweb/silk/init/InitSilk"), ) /** @@ -27,7 +28,10 @@ class UnusedInspectionSuppressor : InspectionSuppressor { override fun isSuppressedFor(element: PsiElement, toolId: String): Boolean { if (toolId != "unused") return false if (!element.isInReadableKobwebProject()) return false - val ktFunction = element.parent as? KtNamedFunction ?: return false + // Originally, only `element.parent` was checked, but at some point it became necessary to check `element` too + val ktFunction = element.parent as? KtNamedFunction + ?: element as? KtNamedFunction + ?: return false return ktFunction.hasAnyAnnotation(ANNOTATION_GENERATES_CODE_KEY, *SUPPRESS_UNUSED_WHEN_ANNOTATED_WITH) } diff --git a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/startup/KobwebPostStartupProjectActivity.kt b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/startup/KobwebPostStartupProjectActivity.kt index ca62ab9..cb1a4fd 100644 --- a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/startup/KobwebPostStartupProjectActivity.kt +++ b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/startup/KobwebPostStartupProjectActivity.kt @@ -1,12 +1,8 @@ package com.varabyte.kobweb.intellij.startup import com.intellij.notification.NotificationType -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.ActionPlaces -import com.intellij.openapi.actionSystem.PlatformDataKeys -import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.components.service -import com.intellij.openapi.externalSystem.action.RefreshAllExternalProjectsAction +import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTracker import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener import com.intellij.openapi.project.Project import com.intellij.openapi.project.modules @@ -50,7 +46,7 @@ private fun Project.hasAnyKobwebDependency(): Boolean { class KobwebPostStartupProjectActivity : ProjectActivity { private class ImportListener( private val project: Project, - private val syncRequestedNotification: KobwebNotificationHandle? + private val syncRequestedNotification: KobwebNotificationHandle?, ) : ProjectDataImportListener { override fun onImportStarted(projectPath: String?) { // If an import is kicked off in an indirect way, we should still dismiss the sync popup. @@ -74,17 +70,13 @@ class KobwebPostStartupProjectActivity : ProjectActivity { project.kobwebPluginState = KobwebPluginState.UNINITIALIZED } - val refreshProjectAction = ActionManager.getInstance().getAction("ExternalSystem.RefreshAllProjects") as? RefreshAllExternalProjectsAction - val syncRequestedNotification = if (refreshProjectAction != null && project.kobwebPluginState == KobwebPluginState.UNINITIALIZED) { + val syncRequestedNotification = if (project.kobwebPluginState == KobwebPluginState.UNINITIALIZED) { KobwebNotifier.Builder("The Kobweb plugin requires a one-time sync to enable functionality.") .type(NotificationType.WARNING) .addAction("Sync Project") { - ActionUtil.invokeAction(refreshProjectAction, { dataId -> - when { - PlatformDataKeys.PROJECT.`is`(dataId) -> project - else -> null - } - }, ActionPlaces.NOTIFICATION, null, null) + val tracker = ExternalSystemProjectTracker.getInstance(project) + tracker.markDirtyAllProjects() + tracker.scheduleProjectRefresh() } .notify(project) } else null diff --git a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/util/psi/KtElementExtensions.kt b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/util/psi/KtElementExtensions.kt index 4a1e8fa..6b8672f 100644 --- a/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/util/psi/KtElementExtensions.kt +++ b/plugin/src/main/kotlin/com/varabyte/kobweb/intellij/util/psi/KtElementExtensions.kt @@ -5,49 +5,28 @@ import com.intellij.openapi.util.Key import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.psi.KtAnnotated -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.psi.KtDeclaration -/** - * Determines whether this [KtAnnotationEntry] has the specified qualified name. - * Careful: this does *not* currently take into account Kotlin type aliases (https://kotlinlang.org/docs/reference/type-aliases.html). - * Fortunately, type aliases are extremely uncommon for simple annotation types. - */ -private fun KtAnnotationEntry.fqNameMatches(fqName: String): Boolean { - // For inspiration, see IDELightClassGenerationSupport.KtUltraLightSupportImpl.findAnnotation in the Kotlin plugin. - val shortName = shortName?.asString() ?: return false - return fqName.endsWith(".$shortName") && fqName == getQualifiedName() +// Code adapted from https://kotlin.github.io/analysis-api/migrating-from-k1.html#using-analysis-api +private fun KtDeclaration.hasAnyAnnotation(vararg classIds: ClassId): Boolean { + analyze(this) { + val annotations = this@hasAnyAnnotation.symbol.annotations + return classIds.any { it in annotations } + } } -/** - * Computes the qualified name of this [KtAnnotationEntry]. - * Prefer to use [fqNameMatches], which checks the short name first and thus has better performance. - */ -private fun KtAnnotationEntry.getQualifiedName(): String? = - analyze(BodyResolveMode.PARTIAL).get(BindingContext.ANNOTATION, this)?.fqName?.asString() - /** * Returns true if the function is tagged with any one of the given annotations. * - * The annotation name must be fully-qualified, as in "androidx.compose.runtime.Composable". - * * @param key A key must be provided to prevent ambiguity errors, as multiple places can call `hasAnyAnnotation` with * different annotation lists on the same target method. */ -internal fun KtAnnotated.hasAnyAnnotation(key: Key>, vararg annotationFqns: String): Boolean { - // Not strictly required but results in a better error message if JB ever reports an issue: - @Suppress("NAME_SHADOWING") val annotationFqns = annotationFqns.toList() - // Code adapted from https://github.com/JetBrains/compose-multiplatform/blob/b501e0f794aecde9a6ce47cb4b5308939cbc7cc5/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/locationUtils.kt#L135 +fun KtDeclaration.hasAnyAnnotation(key: Key>, vararg classIds: ClassId): Boolean { return CachedValuesManager.getCachedValue(this, key) { CachedValueProvider.Result.create( - run { - this.annotationEntries.any { annotationEntry -> - annotationFqns.any { fqName -> annotationEntry.fqNameMatches(fqName) } - } - }, + hasAnyAnnotation(*classIds), this.containingKtFile, ProjectRootModificationTracker.getInstance(project), ) diff --git a/plugin/src/main/resources/META-INF/plugin.xml b/plugin/src/main/resources/META-INF/plugin.xml index 3d6a000..4198e10 100644 --- a/plugin/src/main/resources/META-INF/plugin.xml +++ b/plugin/src/main/resources/META-INF/plugin.xml @@ -26,4 +26,8 @@ + + + +