@@ -21,32 +21,39 @@ import com.intellij.psi.util.CachedValueProvider
21
21
import com.intellij.psi.util.CachedValuesManager
22
22
import com.intellij.psi.util.parentOfType
23
23
import com.intellij.util.concurrency.annotations.RequiresReadLock
24
+ import org.jetbrains.kotlin.analysis.api.analyze
25
+ import org.jetbrains.kotlin.analysis.api.symbols.KaClassLikeSymbol
24
26
import 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
29
33
import 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
33
34
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"
35
37
internal const val COMPOSABLE_FQ_NAME = " androidx.compose.runtime.Composable"
36
38
39
+ private val ComposableAnnotationClassId = ClassId .topLevel(FqName (COMPOSABLE_FQ_NAME ))
40
+ private val DesktopPreviewAnnotationClassId =
41
+ ClassId .topLevel(FqName (DESKTOP_PREVIEW_ANNOTATION_FQN ))
42
+
37
43
/* *
38
44
* Utils based on functions from AOSP, taken from
39
45
* tools/adt/idea/compose-designer/src/com/android/tools/idea/compose/preview/util/PreviewElement.kt
40
46
*/
41
47
42
48
/* *
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:
44
51
* 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
47
54
*/
48
55
private fun KtNamedFunction.isValidPreviewLocation (): Boolean {
49
- if (valueParameters.size > 0 ) return false
56
+ if (valueParameters.isNotEmpty() ) return false
50
57
if (receiverTypeReference != null ) return false
51
58
52
59
if (isTopLevel) return true
@@ -55,7 +62,8 @@ private fun KtNamedFunction.isValidPreviewLocation(): Boolean {
55
62
// This is not a nested method
56
63
val containingClass = containingClass()
57
64
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.
59
67
if (containingClass.isTopLevel() && containingClass.hasDefaultConstructor()) {
60
68
return true
61
69
}
@@ -64,84 +72,67 @@ private fun KtNamedFunction.isValidPreviewLocation(): Boolean {
64
72
return false
65
73
}
66
74
67
-
68
75
/* *
69
76
* Computes the qualified name of the class containing this [KtNamedFunction].
70
77
*
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.
74
80
*/
75
81
internal 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()
78
84
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
+ }
86
96
}
87
- }
97
+
98
+ private fun ClassId?.isKotlinPackage () =
99
+ this != null && startsWith(org.jetbrains.kotlin.builtins.StandardNames .BUILT_INS_PACKAGE_NAME )
88
100
89
101
private fun KtClass.hasDefaultConstructor () =
90
102
allConstructors.isEmpty().or (allConstructors.any { it.valueParameters.isEmpty() })
91
103
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
-
110
104
internal fun KtNamedFunction.composePreviewFunctionFqn () = " ${getClassName()} .${name} "
111
105
112
106
@RequiresReadLock
113
107
internal 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
125
111
126
- return hasComposableAnnotation && hasPreviewAnnotation
127
- }
112
+ val mySymbol = symbol
113
+ val hasComposableAnnotation = mySymbol.annotations.contains(ComposableAnnotationClassId )
114
+ val hasPreviewAnnotation =
115
+ mySymbol.annotations.contains(DesktopPreviewAnnotationClassId )
128
116
129
- return CachedValuesManager .getCachedValue(this ) {
130
- cachedResult(isValidComposablePreviewImpl())
131
- }
117
+ return hasComposableAnnotation && hasPreviewAnnotation
118
+ }
119
+
120
+ return CachedValuesManager .getCachedValue(this ) { cachedResult(isValidComposablePreviewImpl()) }
132
121
}
133
122
134
123
// 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)
138
130
}
139
- }
140
131
141
132
private fun <T > KtNamedFunction.cachedResult (value : T ) =
142
133
CachedValueProvider .Result .create(
143
134
// TODO: see if we can handle alias imports without ruining performance.
144
135
value,
145
136
this .containingKtFile,
146
- ProjectRootModificationTracker .getInstance(project)
147
- )
137
+ ProjectRootModificationTracker .getInstance(project),
138
+ )
0 commit comments