@@ -21,32 +21,39 @@ import com.intellij.psi.util.CachedValueProvider
2121import com.intellij.psi.util.CachedValuesManager
2222import com.intellij.psi.util.parentOfType
2323import com.intellij.util.concurrency.annotations.RequiresReadLock
24+ import org.jetbrains.kotlin.analysis.api.analyze
25+ import org.jetbrains.kotlin.analysis.api.symbols.KaClassLikeSymbol
2426import org.jetbrains.kotlin.asJava.findFacadeClass
25- import org.jetbrains.kotlin.builtins.KotlinBuiltIns
26- import org.jetbrains.kotlin.descriptors.ClassKind
27- import org.jetbrains.kotlin.idea.caches.resolve.analyze
28- import org.jetbrains.kotlin.psi.*
27+ import org.jetbrains.kotlin.name.ClassId
28+ import org.jetbrains.kotlin.name.FqName
29+ import org.jetbrains.kotlin.psi.KtClass
30+ import org.jetbrains.kotlin.psi.KtFile
31+ import org.jetbrains.kotlin.psi.KtNamedFunction
32+ import org.jetbrains.kotlin.psi.allConstructors
2933import org.jetbrains.kotlin.psi.psiUtil.containingClass
30- import org.jetbrains.kotlin.resolve.BindingContext
31- import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
32- import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
3334
34- internal const val DESKTOP_PREVIEW_ANNOTATION_FQN = " androidx.compose.desktop.ui.tooling.preview.Preview"
35+ internal const val DESKTOP_PREVIEW_ANNOTATION_FQN =
36+ " androidx.compose.desktop.ui.tooling.preview.Preview"
3537internal const val COMPOSABLE_FQ_NAME = " androidx.compose.runtime.Composable"
3638
39+ private val ComposableAnnotationClassId = ClassId .topLevel(FqName (COMPOSABLE_FQ_NAME ))
40+ private val DesktopPreviewAnnotationClassId =
41+ ClassId .topLevel(FqName (DESKTOP_PREVIEW_ANNOTATION_FQN ))
42+
3743/* *
3844 * Utils based on functions from AOSP, taken from
3945 * tools/adt/idea/compose-designer/src/com/android/tools/idea/compose/preview/util/PreviewElement.kt
4046 */
4147
4248/* *
43- * Returns whether a `@Composable` [PREVIEW_ANNOTATION_FQN] is defined in a valid location, which can be either:
49+ * Returns whether a `@Composable` [DESKTOP_PREVIEW_ANNOTATION_FQN] is defined in a valid location,
50+ * which can be either:
4451 * 1. Top-level functions
45- * 2. Non-nested functions defined in top-level classes that have a default (no parameter) constructor
46- *
52+ * 2. Non-nested functions defined in top-level classes that have a default (no parameter)
53+ * constructor
4754 */
4855private fun KtNamedFunction.isValidPreviewLocation (): Boolean {
49- if (valueParameters.size > 0 ) return false
56+ if (valueParameters.isNotEmpty() ) return false
5057 if (receiverTypeReference != null ) return false
5158
5259 if (isTopLevel) return true
@@ -55,7 +62,8 @@ private fun KtNamedFunction.isValidPreviewLocation(): Boolean {
5562 // This is not a nested method
5663 val containingClass = containingClass()
5764 if (containingClass != null ) {
58- // We allow functions that are not top level defined in top level classes that have a default (no parameter) constructor.
65+ // We allow functions that are not top level defined in top level classes that have a
66+ // default (no parameter) constructor.
5967 if (containingClass.isTopLevel() && containingClass.hasDefaultConstructor()) {
6068 return true
6169 }
@@ -64,84 +72,67 @@ private fun KtNamedFunction.isValidPreviewLocation(): Boolean {
6472 return false
6573}
6674
67-
6875/* *
6976 * Computes the qualified name of the class containing this [KtNamedFunction].
7077 *
71- * For functions defined within a Kotlin class, returns the qualified name of that class. For top-level functions, returns the JVM name of
72- * the Java facade class generated instead.
73- *
78+ * For functions defined within a Kotlin class, returns the qualified name of that class. For
79+ * top-level functions, returns the JVM name of the Java facade class generated instead.
7480 */
7581internal fun KtNamedFunction.getClassName (): String? =
76- if (isTopLevel) ((parent as ? KtFile )?.findFacadeClass())?.qualifiedName else parentOfType< KtClass >()?.getQualifiedName()
77-
82+ if (isTopLevel) ((parent as ? KtFile )?.findFacadeClass())?.qualifiedName
83+ else parentOfType< KtClass >()?.getQualifiedName()
7884
79- /* * Computes the qualified name for a Kotlin Class. Returns null if the class is a kotlin built-in. */
80- private fun KtClass.getQualifiedName (): String? {
81- val classDescriptor = analyze(BodyResolveMode .PARTIAL ).get(BindingContext .CLASS , this ) ? : return null
82- return if (KotlinBuiltIns .isUnderKotlinPackage(classDescriptor) || classDescriptor.kind != ClassKind .CLASS ) {
83- null
84- } else {
85- classDescriptor.fqNameSafe.asString()
85+ /* *
86+ * Computes the qualified name for a Kotlin Class. Returns null if the class is a kotlin built-in.
87+ */
88+ private fun KtClass.getQualifiedName (): String? =
89+ analyze(this ) {
90+ val classSymbol = symbol
91+ return when {
92+ classSymbol !is KaClassLikeSymbol -> null
93+ classSymbol.classId.isKotlinPackage() -> null
94+ else -> classSymbol.classId?.asFqNameString()
95+ }
8696 }
87- }
97+
98+ private fun ClassId?.isKotlinPackage () =
99+ this != null && startsWith(org.jetbrains.kotlin.builtins.StandardNames .BUILT_INS_PACKAGE_NAME )
88100
89101private fun KtClass.hasDefaultConstructor () =
90102 allConstructors.isEmpty().or (allConstructors.any { it.valueParameters.isEmpty() })
91103
92- /* *
93- * Determines whether this [KtAnnotationEntry] has the specified qualified name.
94- * Careful: this does *not* currently take into account Kotlin type aliases (https://kotlinlang.org/docs/reference/type-aliases.html).
95- * Fortunately, type aliases are extremely uncommon for simple annotation types.
96- */
97- private fun KtAnnotationEntry.fqNameMatches (fqName : String ): Boolean {
98- // For inspiration, see IDELightClassGenerationSupport.KtUltraLightSupportImpl.findAnnotation in the Kotlin plugin.
99- val shortName = shortName?.asString() ? : return false
100- return fqName.endsWith(shortName) && fqName == getQualifiedName()
101- }
102-
103- /* *
104- * Computes the qualified name of this [KtAnnotationEntry].
105- * Prefer to use [fqNameMatches], which checks the short name first and thus has better performance.
106- */
107- private fun KtAnnotationEntry.getQualifiedName (): String? =
108- analyze(BodyResolveMode .PARTIAL ).get(BindingContext .ANNOTATION , this )?.fqName?.asString()
109-
110104internal fun KtNamedFunction.composePreviewFunctionFqn () = " ${getClassName()} .${name} "
111105
112106@RequiresReadLock
113107internal fun KtNamedFunction.isValidComposablePreviewFunction (): Boolean {
114- fun isValidComposablePreviewImpl (): Boolean {
115- if (! isValidPreviewLocation()) return false
116-
117- var hasComposableAnnotation = false
118- var hasPreviewAnnotation = false
119- val annotationIt = annotationEntries.iterator()
120- while (annotationIt.hasNext() && ! (hasComposableAnnotation && hasPreviewAnnotation)) {
121- val annotation = annotationIt.next()
122- hasComposableAnnotation = hasComposableAnnotation || annotation.fqNameMatches(COMPOSABLE_FQ_NAME )
123- hasPreviewAnnotation = hasPreviewAnnotation || annotation.fqNameMatches(DESKTOP_PREVIEW_ANNOTATION_FQN )
124- }
108+ fun isValidComposablePreviewImpl (): Boolean =
109+ analyze(this ) {
110+ if (! isValidPreviewLocation()) return false
125111
126- return hasComposableAnnotation && hasPreviewAnnotation
127- }
112+ val mySymbol = symbol
113+ val hasComposableAnnotation = mySymbol.annotations.contains(ComposableAnnotationClassId )
114+ val hasPreviewAnnotation =
115+ mySymbol.annotations.contains(DesktopPreviewAnnotationClassId )
128116
129- return CachedValuesManager .getCachedValue(this ) {
130- cachedResult(isValidComposablePreviewImpl())
131- }
117+ return hasComposableAnnotation && hasPreviewAnnotation
118+ }
119+
120+ return CachedValuesManager .getCachedValue(this ) { cachedResult(isValidComposablePreviewImpl()) }
132121}
133122
134123// based on AndroidComposePsiUtils.kt from AOSP
135- internal fun KtNamedFunction.isComposableFunction (): Boolean {
136- return CachedValuesManager .getCachedValue(this ) {
137- cachedResult(annotationEntries.any { it.fqNameMatches(COMPOSABLE_FQ_NAME ) })
124+ internal fun KtNamedFunction.isComposableFunction (): Boolean =
125+ CachedValuesManager .getCachedValue(this ) {
126+ val hasComposableAnnotation =
127+ analyze(this ) { symbol.annotations.contains(ComposableAnnotationClassId ) }
128+
129+ cachedResult(hasComposableAnnotation)
138130 }
139- }
140131
141132private fun <T > KtNamedFunction.cachedResult (value : T ) =
142133 CachedValueProvider .Result .create(
143134 // TODO: see if we can handle alias imports without ruining performance.
144135 value,
145136 this .containingKtFile,
146- ProjectRootModificationTracker .getInstance(project)
147- )
137+ ProjectRootModificationTracker .getInstance(project),
138+ )
0 commit comments