From 710940cfb15a64931b3d03066e20e07156c7bb76 Mon Sep 17 00:00:00 2001 From: Xavier Gouchet Date: Wed, 12 Jan 2022 16:58:58 +0100 Subject: [PATCH] Add support to all types in KSP --- .../xgouchet/elmyr/ksp/ForgerableProcessor.kt | 35 +++- .../ksp/ForgerableProcessorProviderTest.kt | 163 ++++++++++++++++++ 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/ksp/src/main/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessor.kt b/ksp/src/main/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessor.kt index 2f2a562..941ad51 100644 --- a/ksp/src/main/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessor.kt +++ b/ksp/src/main/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessor.kt @@ -13,9 +13,13 @@ import com.google.devtools.ksp.symbol.KSNode import com.google.devtools.ksp.symbol.KSValueParameter import com.google.devtools.ksp.symbol.Modifier import com.google.devtools.ksp.visitor.KSDefaultVisitor +import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.BYTE +import com.squareup.kotlinpoet.CHAR import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.DOUBLE +import com.squareup.kotlinpoet.FLOAT import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.INT @@ -164,15 +168,29 @@ class ForgerableProcessor( private fun generateParameterForgery( parameter: KSValueParameter ): CodeBlock { - val typeName = parameter.type.toTypeName() - val forgeryStatement = when (typeName) { - STRING -> "forge.aString()" - BYTE -> "forge.aByte()" - SHORT -> "forge.aShort()" - INT -> "forge.anInt()" - LONG -> "forge.aLong()" - else -> "?" + logger.info("Converting type ${parameter.type}") + val resolvedType = parameter.type.resolve() + val isNullable = resolvedType.isMarkedNullable + val nonNullType = resolvedType.makeNotNullable() + val typeName = nonNullType.toTypeName() + val forgeryMethod = when { + typeName == STRING -> "aString()" + typeName == BOOLEAN -> "aBool()" + typeName == BYTE -> "aByte()" + typeName == SHORT -> "aShort()" + typeName == INT -> "anInt()" + typeName == LONG -> "aLong()" + typeName == FLOAT -> "aFloat()" + typeName == DOUBLE -> "aDouble()" + typeName == CHAR -> "aChar()" + else -> "getForgery()" } + val forgeryStatement = if (isNullable){ + "forge.aNullable { $forgeryMethod }" + } else { + "forge.$forgeryMethod" + } + return CodeBlock.builder() .addStatement( "val %L: %T = %L", @@ -181,7 +199,6 @@ class ForgerableProcessor( forgeryStatement ).build() } - } data class ConstructorContext( diff --git a/ksp/src/test/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessorProviderTest.kt b/ksp/src/test/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessorProviderTest.kt index 1c1ca56..1b35061 100644 --- a/ksp/src/test/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessorProviderTest.kt +++ b/ksp/src/test/kotlin/fr/xgouchet/elmyr/ksp/ForgerableProcessorProviderTest.kt @@ -113,6 +113,167 @@ internal class ForgerableProcessorProviderTest { } } + + @Test + fun processDataClassWithPrimitiveArguments() { + val className = "Spam" + val sourceContent = """ + package com.example + data class $className( + val s: String, + val b: Boolean, + val y: Byte, + val h: Short, + val i: Int, + val l: Long, + val f: Float, + val d: Double, + val c: Char + ) + """ + + testCompilation( + "$className.kt", + sourceContent + ) { + assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + it.assertGeneratedFactoryEquals( + className, + """ + package com.example + + import fr.xgouchet.elmyr.Forge + import fr.xgouchet.elmyr.ForgeryFactory + import kotlin.Boolean + import kotlin.Byte + import kotlin.Char + import kotlin.Double + import kotlin.Float + import kotlin.Int + import kotlin.Long + import kotlin.Short + import kotlin.String + + public class SpamForgeryFactory : ForgeryFactory { + public override fun getForgery(forge: Forge): Spam { + val s: String = forge.aString() + val b: Boolean = forge.aBool() + val y: Byte = forge.aByte() + val h: Short = forge.aShort() + val i: Int = forge.anInt() + val l: Long = forge.aLong() + val f: Float = forge.aFloat() + val d: Double = forge.aDouble() + val c: Char = forge.aChar() + return Spam(s = s, b = b, y = y, h = h, i = i, l = l, f = f, d = d, c = c) + } + } + + """.trimIndent() + ) + } + } + + + @Test + fun processDataClassWithNullablePrimitiveArguments() { + val className = "Spam" + val sourceContent = """ + package com.example + data class $className( + val s: String?, + val b: Boolean?, + val y: Byte?, + val h: Short?, + val i: Int?, + val l: Long?, + val f: Float?, + val d: Double?, + val c: Char? + ) + """ + + testCompilation( + "$className.kt", + sourceContent + ) { + assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + it.assertGeneratedFactoryEquals( + className, + """ + package com.example + + import fr.xgouchet.elmyr.Forge + import fr.xgouchet.elmyr.ForgeryFactory + import kotlin.Boolean + import kotlin.Byte + import kotlin.Char + import kotlin.Double + import kotlin.Float + import kotlin.Int + import kotlin.Long + import kotlin.Short + import kotlin.String + + public class SpamForgeryFactory : ForgeryFactory { + public override fun getForgery(forge: Forge): Spam { + val s: String? = forge.aNullable { aString() } + val b: Boolean? = forge.aNullable { aBool() } + val y: Byte? = forge.aNullable { aByte() } + val h: Short? = forge.aNullable { aShort() } + val i: Int? = forge.aNullable { anInt() } + val l: Long? = forge.aNullable { aLong() } + val f: Float? = forge.aNullable { aFloat() } + val d: Double? = forge.aNullable { aDouble() } + val c: Char? = forge.aNullable { aChar() } + return Spam(s = s, b = b, y = y, h = h, i = i, l = l, f = f, d = d, c = c) + } + } + + """.trimIndent() + ) + } + } + + + @Test + fun processDataClassWithEnumArguments() { + val className = "Bacon" + val sourceContent = """ + package com.example + + enum class Day { MON, TUE, WED, THU, FRI, SAT, SUN } + + data class $className( + val d: Day + ) + """ + + testCompilation( + "$className.kt", + sourceContent + ) { + assertThat(it.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + it.assertGeneratedFactoryEquals( + className, + """ + package com.example + + import fr.xgouchet.elmyr.Forge + import fr.xgouchet.elmyr.ForgeryFactory + + public class BaconForgeryFactory : ForgeryFactory { + public override fun getForgery(forge: Forge): Bacon { + val d: Day = forge.getForgery() + return Bacon(d = d) + } + } + + """.trimIndent() + ) + } + } + @Test fun processDataClassWithSecondaryConst() { val className = "Foo" @@ -208,4 +369,6 @@ internal class ForgerableProcessorProviderTest { javaGeneratedDir.walk().toList() } + // endregion + } \ No newline at end of file