Skip to content

Commit

Permalink
Add option to convert KSAnnotation to AnnotationSpec while omitting d…
Browse files Browse the repository at this point in the history
…efault values (#1538)

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

* feat(ksp): add @jvmoverloads to be binary compatible with previous version

* feat(ksp): replace deepEquals with simple equals check, as the values are never arrays
  • Loading branch information
mcarleio authored Jun 8, 2023
1 parent 9559978 commit ae46c33
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 8 deletions.
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 {
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 @@ -192,6 +220,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 @@ -319,6 +359,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

0 comments on commit ae46c33

Please sign in to comment.