/
annotations.kt
120 lines (114 loc) · 4.52 KB
/
annotations.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
* Copyright (C) 2021 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet.ksp
import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSName
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeAlias
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.ParameterizedTypeName
/** Returns an [AnnotationSpec] representation of this [KSAnnotation] instance. */
@KotlinPoetKspPreview
public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
val builder = when (val type = annotationType.resolve().unwrapTypeAlias().toTypeName()) {
is ClassName -> AnnotationSpec.builder(type)
is ParameterizedTypeName -> AnnotationSpec.builder(type)
else -> error("This is never possible.")
}
useSiteTarget?.let { builder.useSiteTarget(it.kpAnalog) }
// TODO support type params once they're exposed https://github.com/google/ksp/issues/753
for (argument in arguments) {
val member = CodeBlock.builder()
val name = argument.name!!.getShortName()
member.add("%N = ", name)
addValueToBlock(argument.value!!, member)
builder.addMember(member.build())
}
return builder.build()
}
private val AnnotationUseSiteTarget.kpAnalog: UseSiteTarget get() = when (this) {
AnnotationUseSiteTarget.FILE -> UseSiteTarget.FILE
AnnotationUseSiteTarget.PROPERTY -> UseSiteTarget.PROPERTY
AnnotationUseSiteTarget.FIELD -> UseSiteTarget.FIELD
AnnotationUseSiteTarget.GET -> UseSiteTarget.GET
AnnotationUseSiteTarget.SET -> UseSiteTarget.SET
AnnotationUseSiteTarget.RECEIVER -> UseSiteTarget.RECEIVER
AnnotationUseSiteTarget.PARAM -> UseSiteTarget.PARAM
AnnotationUseSiteTarget.SETPARAM -> UseSiteTarget.SETPARAM
AnnotationUseSiteTarget.DELEGATE -> UseSiteTarget.DELEGATE
}
internal fun KSType.unwrapTypeAlias(): KSType {
return if (this.declaration is KSTypeAlias) {
(this.declaration as KSTypeAlias).type.resolve()
} else {
this
}
}
@KotlinPoetKspPreview
private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
when (value) {
is List<*> -> {
// Array type
member.add("arrayOf(⇥⇥")
value.forEachIndexed { index, innerValue ->
if (index > 0) member.add(", ")
addValueToBlock(innerValue!!, member)
}
member.add("⇤⇤)")
}
is KSType -> {
val unwrapped = value.unwrapTypeAlias()
val isEnum = (unwrapped.declaration as KSClassDeclaration).classKind == ClassKind.ENUM_ENTRY
if (isEnum) {
val parent = unwrapped.declaration.parentDeclaration as KSClassDeclaration
val entry = unwrapped.declaration.simpleName.getShortName()
member.add("%T.%L", parent.toClassName(), entry)
} else {
member.add("%T::class", unwrapped.toClassName())
}
}
is KSName ->
member.add(
"%T.%L", ClassName.bestGuess(value.getQualifier()),
value.getShortName()
)
is KSAnnotation -> member.add("%L", value.toAnnotationSpec())
else -> member.add(memberForValue(value))
}
}
/**
* Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
* Handles a number of special cases, such as appending "f" to `Float` values, and uses
* `%L` for other types.
*/
internal fun memberForValue(value: Any) = when (value) {
is Class<*> -> CodeBlock.of("%T::class", value)
is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
is String -> CodeBlock.of("%S", value)
is Float -> CodeBlock.of("%Lf", value)
is Double -> CodeBlock.of("%L", value)
is Char -> CodeBlock.of("$value.toChar()")
is Byte -> CodeBlock.of("$value.toByte()")
is Short -> CodeBlock.of("$value.toShort()")
// Int or Boolean
else -> CodeBlock.of("%L", value)
}