• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 Square, Inc.
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  * https://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 package com.squareup.kotlinpoet.ksp
17 
18 import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
19 import com.google.devtools.ksp.symbol.ClassKind
20 import com.google.devtools.ksp.symbol.KSAnnotation
21 import com.google.devtools.ksp.symbol.KSClassDeclaration
22 import com.google.devtools.ksp.symbol.KSName
23 import com.google.devtools.ksp.symbol.KSType
24 import com.google.devtools.ksp.symbol.KSTypeAlias
25 import com.squareup.kotlinpoet.AnnotationSpec
26 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
27 import com.squareup.kotlinpoet.ClassName
28 import com.squareup.kotlinpoet.CodeBlock
29 import com.squareup.kotlinpoet.ParameterizedTypeName
30 
31 /** Returns an [AnnotationSpec] representation of this [KSAnnotation] instance. */
32 public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
33   val builder = when (val type = annotationType.resolve().unwrapTypeAlias().toTypeName()) {
34     is ClassName -> AnnotationSpec.builder(type)
35     is ParameterizedTypeName -> AnnotationSpec.builder(type)
36     else -> error("This is never possible.")
37   }
38   useSiteTarget?.let { builder.useSiteTarget(it.kpAnalog) }
39   // TODO support type params once they're exposed https://github.com/google/ksp/issues/753
40   for (argument in arguments) {
41     val member = CodeBlock.builder()
42     val name = argument.name!!.getShortName()
43     member.add("%N = ", name)
44     addValueToBlock(argument.value!!, member)
45     builder.addMember(member.build())
46   }
47   return builder.build()
48 }
49 
50 private val AnnotationUseSiteTarget.kpAnalog: UseSiteTarget get() = when (this) {
51   AnnotationUseSiteTarget.FILE -> UseSiteTarget.FILE
52   AnnotationUseSiteTarget.PROPERTY -> UseSiteTarget.PROPERTY
53   AnnotationUseSiteTarget.FIELD -> UseSiteTarget.FIELD
54   AnnotationUseSiteTarget.GET -> UseSiteTarget.GET
55   AnnotationUseSiteTarget.SET -> UseSiteTarget.SET
56   AnnotationUseSiteTarget.RECEIVER -> UseSiteTarget.RECEIVER
57   AnnotationUseSiteTarget.PARAM -> UseSiteTarget.PARAM
58   AnnotationUseSiteTarget.SETPARAM -> UseSiteTarget.SETPARAM
59   AnnotationUseSiteTarget.DELEGATE -> UseSiteTarget.DELEGATE
60 }
61 
unwrapTypeAliasnull62 internal fun KSType.unwrapTypeAlias(): KSType {
63   return if (this.declaration is KSTypeAlias) {
64     (this.declaration as KSTypeAlias).type.resolve()
65   } else {
66     this
67   }
68 }
69 
addValueToBlocknull70 private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
71   when (value) {
72     is List<*> -> {
73       // Array type
74       val arrayType = when (value.firstOrNull()) {
75         is Boolean -> "booleanArrayOf"
76         is Byte -> "byteArrayOf"
77         is Char -> "charArrayOf"
78         is Short -> "shortArrayOf"
79         is Int -> "intArrayOf"
80         is Long -> "longArrayOf"
81         is Float -> "floatArrayOf"
82         is Double -> "doubleArrayOf"
83         else -> "arrayOf"
84       }
85       member.add("$arrayType(⇥⇥")
86       value.forEachIndexed { index, innerValue ->
87         if (index > 0) member.add(", ")
88         addValueToBlock(innerValue!!, member)
89       }
90       member.add("⇤⇤)")
91     }
92     is KSType -> {
93       val unwrapped = value.unwrapTypeAlias()
94       val isEnum = (unwrapped.declaration as KSClassDeclaration).classKind == ClassKind.ENUM_ENTRY
95       if (isEnum) {
96         val parent = unwrapped.declaration.parentDeclaration as KSClassDeclaration
97         val entry = unwrapped.declaration.simpleName.getShortName()
98         member.add("%T.%L", parent.toClassName(), entry)
99       } else {
100         member.add("%T::class", unwrapped.toClassName())
101       }
102     }
103     is KSName ->
104       member.add(
105         "%T.%L",
106         ClassName.bestGuess(value.getQualifier()),
107         value.getShortName(),
108       )
109     is KSAnnotation -> member.add("%L", value.toAnnotationSpec())
110     else -> member.add(memberForValue(value))
111   }
112 }
113 
114 /**
115  * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
116  * Handles a number of special cases, such as appending "f" to `Float` values, and uses
117  * `%L` for other types.
118  */
memberForValuenull119 internal fun memberForValue(value: Any) = when (value) {
120   is Class<*> -> CodeBlock.of("%T::class", value)
121   is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
122   is String -> CodeBlock.of("%S", value)
123   is Float -> CodeBlock.of("%Lf", value)
124   is Double -> CodeBlock.of("%L", value)
125   is Char -> CodeBlock.of("'%L'", value)
126   is Byte -> CodeBlock.of("$value.toByte()")
127   is Short -> CodeBlock.of("$value.toShort()")
128   // Int or Boolean
129   else -> CodeBlock.of("%L", value)
130 }
131