diff options
Diffstat (limited to 'kotlinpoet/src/commonTest/kotlin/com/squareup/kotlinpoet/KotlinPoetTest.kt')
-rw-r--r-- | kotlinpoet/src/commonTest/kotlin/com/squareup/kotlinpoet/KotlinPoetTest.kt | 1441 |
1 files changed, 1441 insertions, 0 deletions
diff --git a/kotlinpoet/src/commonTest/kotlin/com/squareup/kotlinpoet/KotlinPoetTest.kt b/kotlinpoet/src/commonTest/kotlin/com/squareup/kotlinpoet/KotlinPoetTest.kt new file mode 100644 index 00000000..642b65c5 --- /dev/null +++ b/kotlinpoet/src/commonTest/kotlin/com/squareup/kotlinpoet/KotlinPoetTest.kt @@ -0,0 +1,1441 @@ +/* + * Copyright (C) 2017 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 + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.jvm.jvmField +import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards +import java.util.concurrent.TimeUnit +import kotlin.test.Test + +class KotlinPoetTest { + private val tacosPackage = "com.squareup.tacos" + + @Test fun topLevelMembersRetainOrder() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build()) + .addType(TypeSpec.classBuilder("B").build()) + .addProperty( + PropertySpec.builder("c", String::class, KModifier.PUBLIC) + .initializer("%S", "C") + .build(), + ) + .addFunction(FunSpec.builder("d").build()) + .addType(TypeSpec.classBuilder("E").build()) + .addProperty( + PropertySpec.builder("f", String::class, KModifier.PUBLIC) + .initializer("%S", "F") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public fun a() { + |} + | + |public class B + | + |public val c: String = "C" + | + |public fun d() { + |} + | + |public class E + | + |public val f: String = "F" + | + """.trimMargin(), + ) + } + + @Test fun noTopLevelConstructor() { + assertThrows<IllegalArgumentException> { + FileSpec.builder(tacosPackage, "Taco") + .addFunction(FunSpec.constructorBuilder().build()) + } + } + + @Test fun primaryConstructor() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("cheese", String::class) + .beginControlFlow("require(cheese.isNotEmpty())") + .addStatement("%S", "cheese cannot be empty") + .endControlFlow() + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public class Taco( + | cheese: String, + |) { + | init { + | require(cheese.isNotEmpty()) { + | "cheese cannot be empty" + | } + | } + |} + | + """.trimMargin(), + ) + } + + @Test fun primaryConstructorProperties() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("cheese", String::class) + .addParameter("cilantro", String::class) + .addParameter("lettuce", String::class) + .beginControlFlow("require(!cheese.isEmpty())") + .addStatement("%S", "cheese cannot be empty") + .endControlFlow() + .build(), + ) + .addProperty( + PropertySpec.builder("cheese", String::class) + .initializer("cheese") + .build(), + ) + .addProperty( + PropertySpec.builder("cilantro", String::class.asTypeName()) + .mutable() + .initializer("cilantro") + .build(), + ) + .addProperty( + PropertySpec.builder("lettuce", String::class) + .initializer("lettuce.trim()") + .build(), + ) + .addProperty( + PropertySpec.builder("onion", Boolean::class) + .initializer("true") + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Boolean + |import kotlin.String + | + |public class Taco( + | public val cheese: String, + | public var cilantro: String, + | lettuce: String, + |) { + | public val lettuce: String = lettuce.trim() + | + | public val onion: Boolean = true + | init { + | require(!cheese.isEmpty()) { + | "cheese cannot be empty" + | } + | } + |} + | + """.trimMargin(), + ) + } + + @Test fun propertyModifiers() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .addProperty( + PropertySpec.builder("CHEESE", String::class, KModifier.PRIVATE, KModifier.CONST) + .initializer("%S", "monterey jack") + .build(), + ) + .addProperty( + PropertySpec.builder("sauce", String::class.asTypeName(), KModifier.PUBLIC) + .mutable() + .initializer("%S", "chipotle mayo") + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public class Taco { + | private const val CHEESE: String = "monterey jack" + | + | public var sauce: String = "chipotle mayo" + |} + | + """.trimMargin(), + ) + } + + @Test fun mistargetedModifier() { + assertThrows<IllegalArgumentException> { + PropertySpec.builder("CHEESE", String::class, KModifier.DATA).build() + } + } + + @Test fun visibilityModifiers() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build()) + .addFunction(FunSpec.builder("b").addModifiers(KModifier.PROTECTED).build()) + .addFunction(FunSpec.builder("c").addModifiers(KModifier.INTERNAL).build()) + .addFunction(FunSpec.builder("d").addModifiers(KModifier.PRIVATE).build()) + .build(), + ) + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |public class Taco { + | public fun a() { + | } + | + | protected fun b() { + | } + | + | internal fun c() { + | } + | + | private fun d() { + | } + |} + | + """.trimMargin(), + ) + } + + @Test fun strings() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .addFunction( + FunSpec.builder("strings") + .addStatement("val a = %S", "basic string") + .addStatement("val b = %S", "string with a \$ dollar sign") + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + "" + + "package com.squareup.tacos\n" + + "\n" + + "public class Taco {\n" + + " public fun strings() {\n" + + " val a = \"basic string\"\n" + + " val b = \"string with a \${\'\$\'} dollar sign\"\n" + + " }\n" + + "}\n", + ) + } + + /** When emitting a triple quote, KotlinPoet escapes the 3rd quote in the triplet. */ + @Test fun rawStrings() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .addFunction( + FunSpec.builder("strings") + .addStatement("val a = %S", "\"\n") + .addStatement("val b = %S", "a\"\"\"b\"\"\"\"\"\"c\n") + .addStatement( + "val c = %S", + """ + |whoa + |"raw" + |string + """.trimMargin(), + ) + .addStatement( + "val d = %S", + """ + |"raw" + |string + |with + |${'$'}a interpolated value + """.trimMargin(), + ) + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + "" + + "package com.squareup.tacos\n" + + "\n" + + "public class Taco {\n" + + " public fun strings() {\n" + + " val a = \"\"\"\n" + + " |\"\n" + + " |\"\"\".trimMargin()\n" + + " val b = \"\"\"\n" + + " |a\"\"\${'\"'}b\"\"\${'\"'}\"\"\${'\"'}c\n" + + " |\"\"\".trimMargin()\n" + + " val c = \"\"\"\n" + + " |whoa\n" + + " |\"raw\"\n" + + " |string\n" + + " \"\"\".trimMargin()\n" + + " val d = \"\"\"\n" + + " |\"raw\"\n" + + " |string\n" + + " |with\n" + + " |\${\'\$\'}a interpolated value\n" + + " \"\"\".trimMargin()\n" + + " }\n" + + "}\n", + ) + } + + /** + * When a string literal ends in a newline, there's a pipe `|` immediately preceding the closing + * triple quote. Otherwise the closing triple quote has no preceding `|`. + */ + @Test fun edgeCaseStrings() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .addFunction( + FunSpec.builder("strings") + .addStatement("val a = %S", "\n") + .addStatement("val b = %S", " \n ") + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + "" + + "package com.squareup.tacos\n" + + "\n" + + "public class Taco {\n" + + " public fun strings() {\n" + + " val a = \"\"\"\n" + + " |\n" + + " |\"\"\".trimMargin()\n" + + " val b = \"\"\"\n" + + " | \n" + + " | \n" + + " \"\"\".trimMargin()\n" + + " }\n" + + "}\n", + ) + } + + @Test fun parameterDefaultValue() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("Taco") + .addFunction( + FunSpec.builder("addCheese") + .addParameter( + ParameterSpec.builder("kind", String::class) + .defaultValue("%S", "monterey jack") + .build(), + ) + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public class Taco { + | public fun addCheese(kind: String = "monterey jack") { + | } + |} + | + """.trimMargin(), + ) + } + + @Test fun extensionFunction() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("shrink") + .returns(String::class) + .receiver(String::class) + .addStatement("return substring(0, length - 1)") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public fun String.shrink(): String = substring(0, length - 1) + | + """.trimMargin(), + ) + } + + @Test fun extensionFunctionLambda() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("shrink") + .returns(String::class) + .receiver( + LambdaTypeName.get( + parameters = arrayOf(String::class.asClassName()), + returnType = String::class.asTypeName(), + ), + ) + .addStatement("return substring(0, length - 1)") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public fun ((String) -> String).shrink(): String = substring(0, length - 1) + | + """.trimMargin(), + ) + } + + @Test fun extensionFunctionLambdaWithParamName() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("whatever") + .returns(Unit::class) + .receiver( + LambdaTypeName.get( + parameters = arrayOf(ParameterSpec.builder("name", String::class).build()), + returnType = Unit::class.asClassName(), + ), + ) + .addStatement("return Unit") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + |import kotlin.Unit + | + |public fun ((name: String) -> Unit).whatever(): Unit = Unit + | + """.trimMargin(), + ) + } + + @Test fun extensionFunctionLambdaWithMultipleParams() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("whatever") + .returns(Unit::class) + .receiver( + LambdaTypeName.get( + parameters = listOf( + ParameterSpec.builder("name", String::class).build(), + ParameterSpec.unnamed(Int::class), + ParameterSpec.builder("age", Long::class).build(), + ), + returnType = Unit::class.asClassName(), + ), + ) + .addStatement("return Unit") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Int + |import kotlin.Long + |import kotlin.String + |import kotlin.Unit + | + |public fun (( + | name: String, + | Int, + | age: Long, + |) -> Unit).whatever(): Unit = Unit + | + """.trimMargin(), + ) + } + + @Test fun extensionProperty() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addProperty( + PropertySpec.builder("extensionProperty", Int::class) + .receiver(String::class) + .getter( + FunSpec.getterBuilder() + .addStatement("return length") + .build(), + ) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Int + |import kotlin.String + | + |public val String.extensionProperty: Int + | get() = length + | + """.trimMargin(), + ) + } + + @Test fun extensionPropertyLambda() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addProperty( + PropertySpec.builder("extensionProperty", Int::class) + .receiver( + LambdaTypeName.get( + parameters = arrayOf(String::class.asClassName()), + returnType = String::class.asClassName(), + ), + ) + .getter( + FunSpec.getterBuilder() + .addStatement("return length") + .build(), + ) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Int + |import kotlin.String + | + |public val ((String) -> String).extensionProperty: Int + | get() = length + | + """.trimMargin(), + ) + } + + @Test fun nullableTypes() { + val list = (List::class.asClassName().copy(nullable = true) as ClassName) + .parameterizedBy(Int::class.asClassName().copy(nullable = true)) + .copy(nullable = true) + assertThat(list.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?") + } + + @Test fun getAndSet() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addProperty( + PropertySpec.builder("propertyWithCustomAccessors", Int::class.asTypeName()) + .mutable() + .initializer("%L", 1) + .getter( + FunSpec.getterBuilder() + .addStatement("println(%S)", "getter") + .addStatement("return field") + .build(), + ) + .setter( + FunSpec.setterBuilder() + .addParameter("value", Int::class) + .addStatement("println(%S)", "setter") + .addStatement("field = value") + .build(), + ) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Int + | + |public var propertyWithCustomAccessors: Int = 1 + | get() { + | println("getter") + | return field + | } + | set(`value`) { + | println("setter") + | field = value + | } + | + """.trimMargin(), + ) + } + + @Test fun propertyWithLongInitializerWrapping() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addProperty( + PropertySpec + .builder("foo", ClassName(tacosPackage, "Foo").copy(nullable = true)) + .addModifiers(KModifier.PRIVATE) + .initializer("DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |private val foo: Foo? = + | DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file) + | + """.trimMargin(), + ) + } + + @Test fun stackedPropertyModifiers() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addType( + TypeSpec.classBuilder("A") + .addModifiers(KModifier.ABSTRACT) + .addProperty( + PropertySpec.builder("q", String::class.asTypeName()) + .mutable() + .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED) + .build(), + ) + .build(), + ) + .addProperty( + PropertySpec.builder("p", String::class) + .addModifiers(KModifier.CONST, KModifier.INTERNAL) + .initializer("%S", "a") + .build(), + ) + .addType( + TypeSpec.classBuilder("B") + .superclass(ClassName(tacosPackage, "A")) + .addModifiers(KModifier.ABSTRACT) + .addProperty( + PropertySpec.builder("q", String::class.asTypeName()) + .mutable() + .addModifiers( + KModifier.FINAL, + KModifier.LATEINIT, + KModifier.OVERRIDE, + KModifier.PUBLIC, + ) + .build(), + ) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public abstract class A { + | protected abstract var q: String + |} + | + |internal const val p: String = "a" + | + |public abstract class B : A() { + | public final override lateinit var q: String + |} + | + """.trimMargin(), + ) + } + + @Test fun stackedFunModifiers() { + val source = FileSpec.get( + tacosPackage, + TypeSpec.classBuilder("A") + .addModifiers(KModifier.OPEN) + .addFunction( + FunSpec.builder("get") + .addModifiers( + KModifier.EXTERNAL, + KModifier.INFIX, + KModifier.OPEN, + KModifier.OPERATOR, + KModifier.PROTECTED, + ) + .addParameter("v", String::class) + .returns(String::class) + .build(), + ) + .addFunction( + FunSpec.builder("loop") + .addModifiers(KModifier.FINAL, KModifier.INLINE, KModifier.INTERNAL, KModifier.TAILREC) + .returns(String::class) + .addStatement("return %S", "a") + .build(), + ) + .build(), + ) + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public open class A { + | protected open external infix operator fun `get`(v: String): String + | + | internal final tailrec inline fun loop(): String = "a" + |} + | + """.trimMargin(), + ) + } + + @Test fun basicExpressionBody() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("addA") + .addParameter("s", String::class) + .returns(String::class) + .addStatement("return s + %S", "a") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public fun addA(s: String): String = s + "a" + | + """.trimMargin(), + ) + } + + @Test fun suspendingLambdas() { + val barType = ClassName(tacosPackage, "Bar") + val suspendingLambda = LambdaTypeName + .get(parameters = arrayOf(ClassName(tacosPackage, "Foo")), returnType = barType) + .copy(suspending = true) + val source = FileSpec.builder(tacosPackage, "Taco") + .addProperty( + PropertySpec.builder("bar", suspendingLambda) + .mutable() + .initializer("{ %T() }", barType) + .build(), + ) + .addProperty( + PropertySpec.builder("nullBar", suspendingLambda.copy(nullable = true)) + .mutable() + .initializer("null") + .build(), + ) + .addFunction( + FunSpec.builder("foo") + .addParameter("bar", suspendingLambda) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |public var bar: suspend (Foo) -> Bar = { Bar() } + | + |public var nullBar: (suspend (Foo) -> Bar)? = null + | + |public fun foo(bar: suspend (Foo) -> Bar) { + |} + | + """.trimMargin(), + ) + } + + @Test fun enumAsDefaultArgument() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("timeout") + .addParameter("duration", Long::class) + .addParameter( + ParameterSpec.builder("timeUnit", TimeUnit::class) + .defaultValue("%T.%L", TimeUnit::class, TimeUnit.MILLISECONDS.name) + .build(), + ) + .addStatement("this.timeout = timeUnit.toMillis(duration)") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import java.util.concurrent.TimeUnit + |import kotlin.Long + | + |public fun timeout(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) { + | this.timeout = timeUnit.toMillis(duration) + |} + | + """.trimMargin(), + ) + } + + @Test fun dynamicType() { + val source = FileSpec.builder(tacosPackage, "Taco") + .addFunction( + FunSpec.builder("dynamicTest") + .addCode( + CodeBlock.of( + "%L", + PropertySpec.builder("d1", DYNAMIC) + .initializer("%S", "Taco") + .build(), + ), + ) + .addCode( + CodeBlock.of( + "%L", + PropertySpec.builder("d2", DYNAMIC) + .initializer("1f") + .build(), + ), + ) + .addStatement("// dynamics are dangerous!") + .addStatement("println(d1 - d2)") + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |public fun dynamicTest() { + | val d1: dynamic = "Taco" + | val d2: dynamic = 1f + | // dynamics are dangerous! + | println(d1 - d2) + |} + | + """.trimMargin(), + ) + } + + @Test fun primaryConstructorParameterAnnotation() { + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.classBuilder("Taco") + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("foo", String::class) + .build(), + ) + .addProperty( + PropertySpec.builder("foo", String::class) + .jvmField() + .initializer("foo") + .build(), + ) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + |import kotlin.jvm.JvmField + | + |public class Taco( + | @JvmField + | public val foo: String, + |) + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/346 + @Test fun importTypeArgumentInParameterizedTypeName() { + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addFunction( + FunSpec.builder("foo") + .addParameter( + "a", + List::class.asTypeName() + .parameterizedBy(Int::class.asTypeName().jvmSuppressWildcards()), + ) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Int + |import kotlin.collections.List + |import kotlin.jvm.JvmSuppressWildcards + | + |public fun foo(a: List<@JvmSuppressWildcards Int>) { + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/462 + @Test fun foldingPropertyWithLambdaInitializer() { + val param = ParameterSpec.builder("arg", ANY).build() + val initializer = CodeBlock.builder() + .beginControlFlow("{ %L ->", param) + .addStatement("println(\"arg=\$%N\")", param) + .endControlFlow() + .build() + val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias") + val property = PropertySpec.builder("foo", lambdaTypeName) + .initializer("foo") + .build() + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.classBuilder("Taco") + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder("foo", lambdaTypeName) + .defaultValue(initializer) + .build(), + ) + .build(), + ) + .addProperty(property) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import com.example.SomeTypeAlias + | + |public class Taco( + | public val foo: SomeTypeAlias = { arg: kotlin.Any -> + | println("arg=${'$'}arg") + | } + | , + |) + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/483 + @Test fun foldingPropertyWithEscapedName() { + val file = FileSpec.builder("com.squareup.tacos", "AlarmInfo") + .addType( + TypeSpec.classBuilder("AlarmInfo") + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("when", Float::class) + .build(), + ) + .addProperty( + PropertySpec.builder("when", Float::class) + .initializer("when") + .build(), + ) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Float + | + |public class AlarmInfo( + | public val `when`: Float, + |) + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/577 + @Test fun noWrappingBetweenParamNameAndType() { + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addFunction( + FunSpec.builder("functionWithAPrettyLongNameThatWouldCauseWrapping") + .addParameter("parameterWithALongNameThatWouldAlsoCauseWrapping", String::class) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |public + | fun functionWithAPrettyLongNameThatWouldCauseWrapping(parameterWithALongNameThatWouldAlsoCauseWrapping: String) { + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/576 + @Test fun noWrappingBetweenValAndPropertyName() { + val wireField = ClassName("com.squareup.wire", "WireField") + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.classBuilder("Taco") + .addModifiers(KModifier.DATA) + .addProperty( + PropertySpec.builder("name", String::class) + .addAnnotation( + AnnotationSpec.builder(wireField) + .addMember("tag = %L", 1) + .addMember("adapter = %S", "CustomStringAdapterWithALongNameThatCauses") + .build(), + ) + .initializer("name") + .build(), + ) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder("name", String::class) + .build(), + ) + .build(), + ) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import com.squareup.wire.WireField + |import kotlin.String + | + |public data class Taco( + | @WireField( + | tag = 1, + | adapter = "CustomStringAdapterWithALongNameThatCauses", + | ) + | public val name: String, + |) + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/578 + @Test fun wrappingInsideKdocKeepsKdocFormatting() { + val file = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.classBuilder("Builder") + .addKdoc( + "Builder class for Foo. Allows creating instances of Foo by initializing " + + "a subset of their fields, following the Builder pattern.\n", + ) + .addFunction( + FunSpec.builder("summary_text") + .addKdoc( + "The description for the choice, e.g. \"Currently unavailable due to " + + "high demand. Please try later.\" May be null.", + ) + .addParameter("summary_text", String::class.asClassName().copy(nullable = true)) + .returns(ClassName("com.squareup.tacos", "Builder")) + .addStatement("this.summary_text = summary_text") + .addStatement("return this") + .build(), + ) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.String + | + |/** + | * Builder class for Foo. Allows creating instances of Foo by initializing a subset of their fields, + | * following the Builder pattern. + | */ + |public class Builder { + | /** + | * The description for the choice, e.g. "Currently unavailable due to high demand. Please try + | * later." May be null. + | */ + | public fun summary_text(summary_text: String?): Builder { + | this.summary_text = summary_text + | return this + | } + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/606 + @Test fun typeNamesInsideTemplateStringsGetImported() { + val taco = ClassName("com.squareup.tacos", "Taco") + val file = FileSpec.builder("com.squareup.example", "Tacos") + .addFunction( + FunSpec.builder("main") + .addStatement("println(%P)", CodeBlock.of("Here's a taco: \${%T()}", taco)) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.example + | + |import com.squareup.tacos.Taco + | + |public fun main() { + | println(${'"'}""Here's a taco: ${'$'}{Taco()}""${'"'}) + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/606 + @Test fun memberNamesInsideTemplateStringsGetImported() { + val contentToString = MemberName("kotlin.collections", "contentToString") + val file = FileSpec.builder("com.squareup.example", "Tacos") + .addFunction( + FunSpec.builder("main") + .addStatement("val ints = arrayOf(1, 2, 3)") + .addStatement("println(%P)", CodeBlock.of("\${ints.%M()}", contentToString)) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.example + | + |import kotlin.collections.contentToString + | + |public fun main() { + | val ints = arrayOf(1, 2, 3) + | println(${'"'}""${'$'}{ints.contentToString()}""${'"'}) + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/701 + @Test fun noIllegalCharacterInIdentifier() { + assertThrows<IllegalArgumentException> { + TypeSpec.enumBuilder("MyEnum") + .addEnumConstant("with.dots") // dots are illegal, so this should fail + .build().toString() + }.hasMessageThat().isEqualTo("Can't escape identifier `with.dots` because it contains illegal characters: .") + } + + // https://github.com/square/kotlinpoet/issues/814 + @Test fun percentAtTheEndOfKdoc() { + val paramSpec1 = ParameterSpec.builder("a", Int::class) + .addKdoc("Progress in %%") + .build() + val paramSpec2 = ParameterSpec.builder("b", Int::class) + .addKdoc("Some other parameter with %%") + .build() + val funSpec = FunSpec.builder("test") + .addParameters(listOf(paramSpec1, paramSpec2)) + .build() + assertThat(funSpec.toString()).isEqualTo( + """ + |/** + | * @param a Progress in % + | * @param b Some other parameter with % + | */ + |public fun test(a: kotlin.Int, b: kotlin.Int) { + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/1031 + @Test fun superClassGetsFullyQualifiedOnConflict() { + val namespace = "test" + + val kotlinExceptionName = ClassName("kotlin", "Exception") + val customExceptionName = ClassName(namespace, "Exception") + val customException = TypeSpec + .classBuilder("Exception") + .superclass(kotlinExceptionName) + .addFunction( + FunSpec + .builder("test") + .addParameter("e", customExceptionName) + .build(), + ) + .build() + + val file = FileSpec.builder(namespace, "Exception") + .addType(customException) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package test + | + |public class Exception : kotlin.Exception() { + | public fun test(e: Exception) { + | } + |} + | + """.trimMargin(), + ) + } + + @Test fun allStringsAreUnderscore() { + val file = FileSpec.builder("com.squareup.tacos", "SourceWithUnderscores") + .addType( + TypeSpec.classBuilder("SourceWithUnderscores") + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("_", Float::class) + .addParameter("____", Float::class) + .build(), + ) + .addProperty( + PropertySpec.builder("_", Float::class) + .initializer("_") + .build(), + ) + .addProperty( + PropertySpec.builder("____", Float::class) + .initializer("____") + .build(), + ) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import kotlin.Float + | + |public class SourceWithUnderscores( + | public val `_`: Float, + | public val `____`: Float, + |) + | + """.trimMargin(), + ) + } + + @Test fun generatedImportAliases() { + val squareTaco = ClassName("com.squareup.tacos", "Taco") + val blockTaco = ClassName("xyz.block.tacos", "Taco") + val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty") + val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty") + val file = FileSpec.builder("com.example", "Test") + .addFunction( + FunSpec.builder("main") + .addStatement("val squareTaco = %L", squareTaco.constructorReference()) + .addStatement("val blockTaco = %L", blockTaco.constructorReference()) + .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty) + .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.example + | + |import com.squareup.cash.util.isNullOrEmpty as utilIsNullOrEmpty + |import com.squareup.tacos.Taco as SquareupTacosTaco + |import kotlin.text.isNullOrEmpty as textIsNullOrEmpty + |import xyz.block.tacos.Taco as BlockTacosTaco + | + |public fun main() { + | val squareTaco = ::SquareupTacosTaco + | val blockTaco = ::BlockTacosTaco + | val isSquareTacoNull = "Taco".textIsNullOrEmpty() + | val isBlockTacoNull = "Taco".utilIsNullOrEmpty() + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/1518 + @Test fun generatedImportAliasesSamePackageDifferentContainingClasses() { + val strokeCapRound = MemberName( + enclosingClassName = ClassName("androidx.compose.ui.graphics", "StrokeCap").nestedClass("Companion"), + simpleName = "Round", + ) + val strokeJoinRound = MemberName( + enclosingClassName = ClassName("androidx.compose.ui.graphics", "StrokeJoin").nestedClass("Companion"), + simpleName = "Round", + ) + val file = FileSpec.builder("com.example", "Test") + .addFunction( + FunSpec.builder("main") + .addStatement("val strokeCapRound = %M()", strokeCapRound) + .addStatement("val strokeJoinRound = %M()", strokeJoinRound) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.example + | + |import androidx.compose.ui.graphics.StrokeCap.Companion.Round as strokeCapRound + |import androidx.compose.ui.graphics.StrokeJoin.Companion.Round as strokeJoinRound + | + |public fun main() { + | val strokeCapRound = strokeCapRound() + | val strokeJoinRound = strokeJoinRound() + |} + | + """.trimMargin(), + ) + } + + @Test fun memberImportsOverGeneratedImportAliases() { + val squareTaco = ClassName("com.squareup.tacos", "Taco") + val blockTaco = ClassName("xyz.block.tacos", "Taco") + val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty") + val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty") + val file = FileSpec.builder("com.example", "Test") + .addAliasedImport(squareTaco, "SquareTaco") + .addAliasedImport(blockTaco, "BlockTaco") + .addAliasedImport(kotlinIsNullOrEmpty, "kotlinIsNullOrEmpty") + .addAliasedImport(cashIsNullOrEmpty, "cashIsNullOrEmpty") + .addFunction( + FunSpec.builder("main") + .addStatement("val squareTaco = %L", squareTaco.constructorReference()) + .addStatement("val blockTaco = %L", blockTaco.constructorReference()) + .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty) + .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty) + .build(), + ) + .build() + assertThat(file.toString()).isEqualTo( + """ + |package com.example + | + |import com.squareup.cash.util.isNullOrEmpty as cashIsNullOrEmpty + |import com.squareup.tacos.Taco as SquareTaco + |import kotlin.text.isNullOrEmpty as kotlinIsNullOrEmpty + |import xyz.block.tacos.Taco as BlockTaco + | + |public fun main() { + | val squareTaco = ::SquareTaco + | val blockTaco = ::BlockTaco + | val isSquareTacoNull = "Taco".kotlinIsNullOrEmpty() + | val isBlockTacoNull = "Taco".cashIsNullOrEmpty() + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/1563 + @Test fun nestedClassesWithConflictingAutoGeneratedImports() { + val source = FileSpec.builder("com.squareup.tacos", "Taco") + .addType( + TypeSpec.classBuilder("Taco") + .addProperty("madeFreshDate", ClassName("java.util", "Date", "Builder")) + .addProperty("madeFreshDatabaseDate", ClassName("java.sql", "Date", "Builder")) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import java.sql.Date as SqlDate + |import java.util.Date as UtilDate + | + |public class Taco { + | public val madeFreshDate: UtilDate.Builder + | + | public val madeFreshDatabaseDate: SqlDate.Builder + |} + | + """.trimMargin(), + ) + } + + // https://github.com/square/kotlinpoet/issues/1563 + @Test fun nestedClassesWithConflictingManuallySuppliedImports() { + val source = FileSpec.builder("com.squareup.tacos", "Taco") + .addAliasedImport(ClassName("java.util", "Date"), "UtilDate") + .addAliasedImport(ClassName("java.sql", "Date"), "SqlDate") + .addType( + TypeSpec.classBuilder("Taco") + .addProperty("madeFreshDate", ClassName("java.util", "Date", "Builder")) + .addProperty("madeFreshDatabaseDate", ClassName("java.sql", "Date", "Builder")) + .build(), + ) + .build() + assertThat(source.toString()).isEqualTo( + """ + |package com.squareup.tacos + | + |import java.sql.Date as SqlDate + |import java.util.Date as UtilDate + | + |public class Taco { + | public val madeFreshDate: UtilDate.Builder + | + | public val madeFreshDatabaseDate: SqlDate.Builder + |} + | + """.trimMargin(), + ) + } +} |