com.squareup.kotlinpoet.ksp.Annotations.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlinpoet-ksp Show documentation
Show all versions of kotlinpoet-ksp Show documentation
Extensions for interop with KSP (Kotlin Symbol Processing).
The newest version!
/*
* 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.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.
* @param omitDefaultValues omit defining default values when `true`
*/
@JvmOverloads
public fun KSAnnotation.toAnnotationSpec(omitDefaultValues: Boolean = false): AnnotationSpec {
val typeName = annotationType.resolve().toTypeName()
val builder = if (typeName is ClassName) {
AnnotationSpec.builder(typeName)
} else {
AnnotationSpec.builder(typeName as ParameterizedTypeName)
}
val params = annotationType.resolve()
.resolveKSClassDeclaration()?.primaryConstructor?.parameters.orEmpty()
.associateBy { it.name }
useSiteTarget?.let { builder.useSiteTarget(it.kpAnalog) }
var varargValues: List<*>? = null
for (argument in arguments) {
val value = argument.value ?: continue
val name = argument.name!!.getShortName()
val type = params[argument.name]
if (omitDefaultValues) {
val defaultValue = this.defaultArguments.firstOrNull { it.name?.asString() == name }?.value
if (isDefaultValue(value, defaultValue)) {
continue
}
}
if (type?.isVararg == true) {
// Wait to add varargs to end.
varargValues = value as List<*>
} else {
val member = CodeBlock.builder()
member.add("%N = ", name)
addValueToBlock(value, member, omitDefaultValues)
builder.addMember(member.build())
}
}
if (varargValues != null) {
for (item in varargValues) {
val member = CodeBlock.builder()
addValueToBlock(item!!, member, omitDefaultValues)
builder.addMember(member.build())
}
}
return builder.build()
}
private fun isDefaultValue(value: Any?, defaultValue: Any?): Boolean {
if (defaultValue == null) return false
if (value is KSAnnotation && defaultValue is KSAnnotation) {
return defaultValue.defaultArguments.all { defaultValueArg ->
isDefaultValue(value.arguments.firstOrNull { it.name == defaultValueArg.name }?.value, defaultValueArg.value)
}
}
if (value is List<*> && defaultValue is List<*>) {
return value.size == defaultValue.size && defaultValue.indices.all { index ->
isDefaultValue(value[index], defaultValue[index])
}
}
return value == defaultValue
}
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
}
private fun addValueToBlock(value: Any, member: CodeBlock.Builder, omitDefaultValues: Boolean) {
when (value) {
is List<*> -> {
// Array type
val arrayType = when (value.firstOrNull()) {
is Boolean -> "booleanArrayOf"
is Byte -> "byteArrayOf"
is Char -> "charArrayOf"
is Short -> "shortArrayOf"
is Int -> "intArrayOf"
is Long -> "longArrayOf"
is Float -> "floatArrayOf"
is Double -> "doubleArrayOf"
else -> "arrayOf"
}
member.add("$arrayType(⇥⇥")
value.forEachIndexed { index, innerValue ->
if (index > 0) member.add(", ")
addValueToBlock(innerValue!!, member, omitDefaultValues)
}
member.add("⇤⇤)")
}
is KSType -> {
val declaration = value.resolveKSClassDeclaration() ?: error("Cannot resolve type of $value")
val isEnum = declaration.classKind == ClassKind.ENUM_ENTRY
if (isEnum) {
val parent = declaration.parentDeclaration?.resolveKSClassDeclaration()
?: error("Could not resolve enclosing enum class of entry ${declaration.qualifiedName?.asString()}")
val entry = declaration.simpleName.getShortName()
member.add("%T.%L", parent.toClassName(), entry)
} else {
member.add("%T::class", declaration.toClassName())
}
}
is KSClassDeclaration -> {
check(value.classKind == ClassKind.ENUM_ENTRY)
member.add(
"%T",
value.toClassName(),
)
}
is KSName ->
member.add(
"%T.%L",
ClassName.bestGuess(value.getQualifier()),
value.getShortName(),
)
is KSAnnotation -> member.add("%L", value.toAnnotationSpec(omitDefaultValues))
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("'%L'", value)
is Byte -> CodeBlock.of("$value.toByte()")
is Short -> CodeBlock.of("$value.toShort()")
// Int or Boolean
else -> CodeBlock.of("%L", value)
}