Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ksp): add option to convert KSAnnotation to AnnotationSpec while omitting default values #1538

Merged
merged 3 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions interop/ksp/api/ksp.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
public final class com/squareup/kotlinpoet/ksp/AnnotationsKt {
public static final fun toAnnotationSpec (Lcom/google/devtools/ksp/symbol/KSAnnotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
public static final fun toAnnotationSpec (Lcom/google/devtools/ksp/symbol/KSAnnotation;Z)Lcom/squareup/kotlinpoet/AnnotationSpec;
public static synthetic fun toAnnotationSpec$default (Lcom/google/devtools/ksp/symbol/KSAnnotation;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec;
}

public final class com/squareup/kotlinpoet/ksp/KsClassDeclarationsKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.ParameterizedTypeName

/** Returns an [AnnotationSpec] representation of this [KSAnnotation] instance. */
public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
/**
* 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 {
mcarleio marked this conversation as resolved.
Show resolved Hide resolved
val builder = when (val type = annotationType.resolve().unwrapTypeAlias().toTypeName()) {
is ClassName -> AnnotationSpec.builder(type)
is ParameterizedTypeName -> AnnotationSpec.builder(type)
Expand All @@ -39,15 +43,34 @@ public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
// TODO support type params once they're exposed https://github.com/google/ksp/issues/753
for (argument in arguments) {
val value = argument.value ?: continue
val member = CodeBlock.builder()
val name = argument.name!!.getShortName()
if (omitDefaultValues) {
val defaultValue = this.defaultArguments.firstOrNull { it.name?.asString() == name }?.value
if (isDefaultValue(value, defaultValue)) { continue }
}
val member = CodeBlock.builder()
member.add("%N = ", name)
addValueToBlock(value, member)
addValueToBlock(value, 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 && (0 until defaultValue.size).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
Expand All @@ -68,7 +91,7 @@ internal fun KSType.unwrapTypeAlias(): KSType {
}
}

private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
private fun addValueToBlock(value: Any, member: CodeBlock.Builder, omitDefaultValues: Boolean) {
when (value) {
is List<*> -> {
// Array type
Expand All @@ -86,7 +109,7 @@ private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
member.add("$arrayType(⇥⇥")
value.forEachIndexed { index, innerValue ->
if (index > 0) member.add(", ")
addValueToBlock(innerValue!!, member)
addValueToBlock(innerValue!!, member, omitDefaultValues)
}
member.add("⇤⇤)")
}
Expand All @@ -107,7 +130,7 @@ private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
ClassName.bestGuess(value.getQualifier()),
value.getShortName(),
)
is KSAnnotation -> member.add("%L", value.toAnnotationSpec())
is KSAnnotation -> member.add("%L", value.toAnnotationSpec(omitDefaultValues))
else -> member.add(memberForValue(value))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class TestProcessor(private val env: SymbolProcessorEnvironment) : SymbolProcess
addAnnotations(
decl.annotations
.filterNot { it.shortName.getShortName() == "ExampleAnnotation" }
.map { it.toAnnotationSpec() }.asIterable(),
.map { it.toAnnotationSpec(it.shortName.getShortName() == "ExampleAnnotationWithDefaults") }
.asIterable(),
)
val allSupertypes = decl.superTypes.toList()
val (superclassReference, superInterfaces) = if (allSupertypes.isNotEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ package com.squareup.kotlinpoet.ksp.test.processor
import kotlin.reflect.KClass

annotation class ExampleAnnotation
annotation class ExampleAnnotationWithDefaults(
val boolean: Boolean = true,
val booleanArray: BooleanArray = [true],
val byte: Byte = 1,
val byteArray: ByteArray = [1],
val char: Char = 'C',
val charArray: CharArray = ['C'],
val short: Short = 1,
val shortArray: ShortArray = [1],
val int: Int = 1,
val intArray: IntArray = [1],
val long: Long = 1,
val longArray: LongArray = [1],
val float: Float = 1.0f,
val floatArray: FloatArray = [1.0f],
val double: Double = 1.0,
val doubleArray: DoubleArray = [1.0],
val string: String = "",
val stringArray: Array<String> = [""],
val someClass: KClass<*> = String::class,
val someClasses: Array<KClass<*>> = [String::class],
val enumValue: AnnotationEnumValue = AnnotationEnumValue.ONE,
val enumValueArray: Array<AnnotationEnumValue> = [AnnotationEnumValue.ONE],
val anotherAnnotation: AnotherAnnotation = AnotherAnnotation(""),
val anotherAnnotationArray: Array<AnotherAnnotation> = [AnotherAnnotation("")],
)

annotation class ComprehensiveAnnotation<T : CharSequence>(
val boolean: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class TestProcessorTest {
import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.ComprehensiveAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotationWithDefaults

typealias TypeAliasName = String
typealias GenericTypeAlias = List<String>
Expand Down Expand Up @@ -77,6 +78,32 @@ class TestProcessorTest {
anotherAnnotation = AnotherAnnotation("Hello"),
anotherAnnotationArray = [AnotherAnnotation("Hello")]
)
@ExampleAnnotationWithDefaults(
true, // Omit the name intentionally here to test names are still picked up
booleanArray = [false],
byte = 0.toByte(),
byteArray = [1.toByte()],
char = 'C',
charArray = ['C'],
short = 0.toShort(),
shortArray = [1.toShort()],
int = 0,
intArray = [1],
long = 0L,
longArray = [1L],
float = 0f,
floatArray = [1f],
double = 1.0,
doubleArray = [0.0],
string = "Hello",
stringArray = [""],
someClass = String::class,
someClasses = [Int::class],
enumValue = AnnotationEnumValue.ONE,
enumValueArray = [AnnotationEnumValue.ONE, AnnotationEnumValue.TWO],
anotherAnnotation = AnotherAnnotation(""),
anotherAnnotationArray = [AnotherAnnotation("Hello")]
)
@ExampleAnnotation
class SmokeTestClass<T, R : Any, E : Enum<E>> {
@field:AnotherAnnotation("siteTargeting")
Expand Down Expand Up @@ -150,6 +177,7 @@ class TestProcessorTest {
import com.squareup.kotlinpoet.ksp.test.processor.AnnotationEnumValue
import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.ComprehensiveAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotationWithDefaults
import kotlin.Any
import kotlin.Array
import kotlin.Boolean
Expand Down Expand Up @@ -193,6 +221,18 @@ class TestProcessorTest {
anotherAnnotationArray = arrayOf(AnotherAnnotation(input = "Hello")),
defaultingString = "defaultValue",
)
@ExampleAnnotationWithDefaults(
booleanArray = booleanArrayOf(false),
byte = 0.toByte(),
short = 0.toShort(),
int = 0,
long = 0,
float = 0.0f,
doubleArray = doubleArrayOf(0.0),
string = "Hello",
someClasses = arrayOf(Int::class),
enumValueArray = arrayOf(AnnotationEnumValue.ONE, AnnotationEnumValue.TWO),
)
public class SmokeTestClass<T, R : Any, E : Enum<E>> {
@field:AnotherAnnotation(input = "siteTargeting")
private val propA: String
Expand Down Expand Up @@ -321,6 +361,75 @@ class TestProcessorTest {
)
}

@Test
fun removeDefaultValues() {
val compilation = prepareCompilation(
kotlin(
"Example.kt",
"""
package test

import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotationWithDefaults
import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
import com.squareup.kotlinpoet.ksp.test.processor.AnnotationEnumValue

@ExampleAnnotation
@ExampleAnnotationWithDefaults(
true, // Omit the name intentionally here to test names are still picked up
booleanArray = [true],
byte = 1.toByte(),
byteArray = [1.toByte()],
char = 'C',
charArray = ['C'],
short = 1.toShort(),
shortArray = [1.toShort()],
int = 1,
intArray = [1],
long = 1L,
longArray = [1L],
float = 1f,
floatArray = [1f],
double = 1.0,
doubleArray = [1.0],
string = "",
stringArray = [""],
someClass = String::class,
someClasses = [String::class],
enumValue = AnnotationEnumValue.ONE,
enumValueArray = [AnnotationEnumValue.ONE],
anotherAnnotation = AnotherAnnotation(""),
anotherAnnotationArray = [AnotherAnnotation("")]
)
open class Node<T : Node<T, R>, R : Node<R, T>> {
var t: T? = null
var r: R? = null
}
""",
),
)

val result = compilation.compile()
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestNode.kt")
.readText()
assertThat(generatedFileText).isEqualTo(
"""
package test

import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotationWithDefaults

@ExampleAnnotationWithDefaults
public open class Node<T : Node<T, R>, R : Node<R, T>> {
public var t: T?

public var r: R?
}

""".trimIndent(),
)
}

@Test
fun complexSelfReferencingTypeArgs() {
val compilation = prepareCompilation(
Expand Down