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