1 /*
2 * Copyright 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.appfunctions.compiler.core
18
19 import com.google.devtools.ksp.symbol.KSAnnotation
20 import com.google.devtools.ksp.symbol.KSClassDeclaration
21 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
22 import com.google.devtools.ksp.symbol.KSName
23 import com.google.devtools.ksp.symbol.KSType
24 import com.google.devtools.ksp.symbol.KSTypeArgument
25 import com.google.devtools.ksp.symbol.KSTypeParameter
26 import com.google.devtools.ksp.symbol.KSTypeReference
27 import com.google.devtools.ksp.symbol.Variance
28 import com.google.devtools.ksp.symbol.Variance.CONTRAVARIANT
29 import com.google.devtools.ksp.symbol.Variance.COVARIANT
30 import com.google.devtools.ksp.symbol.Variance.INVARIANT
31 import com.squareup.kotlinpoet.ClassName
32 import com.squareup.kotlinpoet.LIST
33 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
34 import com.squareup.kotlinpoet.STAR
35 import com.squareup.kotlinpoet.TypeName
36 import com.squareup.kotlinpoet.TypeVariableName
37 import com.squareup.kotlinpoet.WildcardTypeName
38 import kotlin.reflect.KClass
39 import kotlin.reflect.cast
40
41 /** Gets the [TypeVariableName] from [KSTypeParameter]. */
toTypeVariableNamenull42 fun KSTypeParameter.toTypeVariableName(): TypeVariableName {
43 return TypeVariableName(name.asString())
44 }
45
46 /**
47 * Gets the qualified name from [KSFunctionDeclaration].
48 *
49 * @throws ProcessingException if unable to resolve qualified name.
50 */
KSFunctionDeclarationnull51 fun KSFunctionDeclaration.ensureQualifiedName(): String {
52 return this.qualifiedName?.asString()
53 ?: throw ProcessingException("Unable to resolve the qualified name", this)
54 }
55
56 /** Gets [ClassName] from [KSClassDeclaration]. */
KSClassDeclarationnull57 fun KSClassDeclaration.toClassName(): ClassName {
58 val packageName = this.packageName.asString()
59 val simpleName = this.simpleName.asString()
60 return ClassName(packageName, simpleName)
61 }
62
63 /**
64 * Resolves the type reference to the parameterized type if it is a list.
65 *
66 * @return the resolved type reference
67 * @throws ProcessingException If unable to resolve the type.
68 */
KSTypeReferencenull69 fun KSTypeReference.resolveListParameterizedType(): KSTypeReference {
70 if (!isOfType(LIST)) {
71 throw ProcessingException(
72 "Unable to resolve list parameterized type for non list type",
73 this
74 )
75 }
76 return resolve().arguments.firstOrNull()?.type
77 ?: throw ProcessingException("Unable to resolve the parameterized type for the list", this)
78 }
79
80 /**
81 * Checks if the type reference is of the given type.
82 *
83 * @param type the type to check against
84 * @return true if the type reference is of the given type
85 * @throws ProcessingException If unable to resolve the type.
86 */
KSTypeReferencenull87 fun KSTypeReference.isOfType(type: ClassName): Boolean {
88 val typeName = ensureQualifiedTypeName()
89 return typeName.asString() == type.canonicalName
90 }
91
92 /**
93 * Finds and returns an annotation of [annotationClass] type.
94 *
95 * @param annotationClass the annotation class to find
96 */
findAnnotationnull97 fun Sequence<KSAnnotation>.findAnnotation(annotationClass: ClassName): KSAnnotation? =
98 this.singleOrNull {
99 val shortName = it.shortName.getShortName()
100 if (shortName != annotationClass.simpleName) {
101 false
102 } else {
103 val typeName = it.annotationType.ensureQualifiedTypeName()
104 typeName.asString() == annotationClass.canonicalName
105 }
106 }
107
108 /**
109 * Resolves the type reference to its qualified name.
110 *
111 * @return the qualified name of the type reference
112 */
KSTypeReferencenull113 fun KSTypeReference.ensureQualifiedTypeName(): KSName =
114 resolve().declaration.qualifiedName
115 ?: throw ProcessingException(
116 "Unable to resolve the qualified type name for this reference",
117 this
118 )
119
120 /** Returns the value of the annotation property if found. */
121 fun <T : Any> KSAnnotation.requirePropertyValueOfType(
122 propertyName: String,
123 expectedType: KClass<T>,
124 ): T {
125 val propertyValue =
126 this.arguments.singleOrNull { it.name?.asString() == propertyName }?.value
127 ?: throw ProcessingException("Unable to find property with name: $propertyName", this)
128 return expectedType.cast(propertyValue)
129 }
130
131 // TODO: Import KotlinPoet KSP to replace these KSPUtils.
KSTypeReferencenull132 fun KSTypeReference.toTypeName(): TypeName {
133 val args = resolve().arguments
134 return resolve().toTypeName(args)
135 }
136
ignoreNullablenull137 internal fun TypeName.ignoreNullable(): TypeName {
138 return copy(nullable = false)
139 }
140
KSTypenull141 private fun KSType.toTypeName(arguments: List<KSTypeArgument> = emptyList()): TypeName {
142 val type =
143 when (declaration) {
144 is KSClassDeclaration -> {
145 val typeClassName =
146 ClassName(declaration.packageName.asString(), declaration.simpleName.asString())
147 typeClassName.withTypeArguments(arguments.map { it.toTypeName() })
148 }
149 else -> throw ProcessingException("Unable to resolve TypeName", null)
150 }
151 return type.copy(nullable = isMarkedNullable)
152 }
153
toTypeNamenull154 private fun KSTypeArgument.toTypeName(): TypeName {
155 val type = this.type ?: return STAR
156 return when (variance) {
157 COVARIANT -> WildcardTypeName.producerOf(type.toTypeName())
158 CONTRAVARIANT -> WildcardTypeName.consumerOf(type.toTypeName())
159 Variance.STAR -> STAR
160 INVARIANT -> type.toTypeName()
161 }
162 }
163
ClassNamenull164 private fun ClassName.withTypeArguments(arguments: List<TypeName>): TypeName {
165 return if (arguments.isEmpty()) {
166 this
167 } else {
168 this.parameterizedBy(arguments)
169 }
170 }
171
KClassnull172 fun KClass<*>.ensureQualifiedName(): String = checkNotNull(qualifiedName)
173