Skip to content

Commit

Permalink
Update logic for checking annotations to avoid using internal analyze…
Browse files Browse the repository at this point in the history
… API

Bug #49
  • Loading branch information
bitspittle committed Jun 24, 2024
1 parent 6de415c commit e720433
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@ import com.intellij.codeInspection.SuppressQuickFix
import com.intellij.psi.PsiElement
import com.varabyte.kobweb.intellij.util.kobweb.isInKobwebSource
import com.varabyte.kobweb.intellij.util.kobweb.isInReadableKobwebProject
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.annotations.annotationClassIds
import com.varabyte.kobweb.intellij.util.psi.hasAnyAnnotation
import org.jetbrains.kotlin.psi.KtNamedFunction

private val SUPPRESS_FUNCTION_NAME_WHEN_ANNOTATED_WITH = arrayOf(
"androidx.compose.runtime.Composable",
)

/**
* Suppress the "Function name should start with a lowercase letter" inspection.
*/
Expand All @@ -22,15 +17,7 @@ class FunctionNameInspectionSuppressor : InspectionSuppressor {
if (!element.isInReadableKobwebProject() && !element.isInKobwebSource()) return false
val ktFunction = element.parent as? KtNamedFunction ?: return false

analyze(ktFunction) {
val symbol = ktFunction.getSymbol()

symbol.annotationClassIds.forEach {
if (it.asFqNameString() in SUPPRESS_FUNCTION_NAME_WHEN_ANNOTATED_WITH) return true
}
}

return false
return ktFunction.hasAnyAnnotation("androidx.compose.runtime.Composable")
}

override fun getSuppressActions(element: PsiElement?, toolId: String) = emptyArray<SuppressQuickFix>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import com.intellij.codeInspection.InspectionSuppressor
import com.intellij.codeInspection.SuppressQuickFix
import com.intellij.psi.PsiElement
import com.varabyte.kobweb.intellij.util.kobweb.isInReadableKobwebProject
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.annotations.annotationClassIds
import com.varabyte.kobweb.intellij.util.psi.hasAnyAnnotation
import org.jetbrains.kotlin.psi.KtNamedFunction

private val SUPPRESS_UNUSED_WHEN_ANNOTATED_WITH = arrayOf(
Expand All @@ -26,15 +25,7 @@ class UnusedInspectionSuppressor : InspectionSuppressor {
if (!element.isInReadableKobwebProject()) return false
val ktFunction = element.parent as? KtNamedFunction ?: return false

analyze(ktFunction) {
val symbol = ktFunction.getSymbol()

symbol.annotationClassIds.forEach {
if (it.asFqNameString() in SUPPRESS_UNUSED_WHEN_ANNOTATED_WITH) return true
}
}

return false
return ktFunction.hasAnyAnnotation(*SUPPRESS_UNUSED_WHEN_ANNOTATED_WITH)
}

override fun getSuppressActions(element: PsiElement?, toolId: String) = emptyArray<SuppressQuickFix>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.varabyte.kobweb.intellij.util.psi

import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.kotlin.idea.base.psi.kotlinFqName
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

/**
* 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()
}

/**
* 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".
*/
internal fun KtAnnotated.hasAnyAnnotation(vararg annotationFqns: String): Boolean {
// 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
return CachedValuesManager.getCachedValue(this) {
CachedValueProvider.Result.create(
run {
this.annotationEntries.any { annotationEntry ->
annotationFqns.any { fqName -> annotationEntry.fqNameMatches(fqName) }
}
},
this.containingKtFile,
ProjectRootModificationTracker.getInstance(project),
*annotationFqns
)
}
}

0 comments on commit e720433

Please sign in to comment.