• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.kotlinpoet
17 
18 import com.google.common.truth.Truth.assertThat
19 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
20 import com.squareup.kotlinpoet.jvm.jvmField
21 import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards
22 import java.util.concurrent.TimeUnit
23 import kotlin.test.Test
24 
25 class KotlinPoetTest {
26   private val tacosPackage = "com.squareup.tacos"
27 
topLevelMembersRetainOrdernull28   @Test fun topLevelMembersRetainOrder() {
29     val source = FileSpec.builder(tacosPackage, "Taco")
30       .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build())
31       .addType(TypeSpec.classBuilder("B").build())
32       .addProperty(
33         PropertySpec.builder("c", String::class, KModifier.PUBLIC)
34           .initializer("%S", "C")
35           .build(),
36       )
37       .addFunction(FunSpec.builder("d").build())
38       .addType(TypeSpec.classBuilder("E").build())
39       .addProperty(
40         PropertySpec.builder("f", String::class, KModifier.PUBLIC)
41           .initializer("%S", "F")
42           .build(),
43       )
44       .build()
45     assertThat(source.toString()).isEqualTo(
46       """
47         |package com.squareup.tacos
48         |
49         |import kotlin.String
50         |import kotlin.Unit
51         |
52         |public fun a(): Unit {
53         |}
54         |
55         |public class B
56         |
57         |public val c: String = "C"
58         |
59         |public fun d(): Unit {
60         |}
61         |
62         |public class E
63         |
64         |public val f: String = "F"
65         |
66       """.trimMargin(),
67     )
68   }
69 
noTopLevelConstructornull70   @Test fun noTopLevelConstructor() {
71     assertThrows<IllegalArgumentException> {
72       FileSpec.builder(tacosPackage, "Taco")
73         .addFunction(FunSpec.constructorBuilder().build())
74     }
75   }
76 
primaryConstructornull77   @Test fun primaryConstructor() {
78     val source = FileSpec.get(
79       tacosPackage,
80       TypeSpec.classBuilder("Taco")
81         .primaryConstructor(
82           FunSpec.constructorBuilder()
83             .addParameter("cheese", String::class)
84             .beginControlFlow("require(cheese.isNotEmpty())")
85             .addStatement("%S", "cheese cannot be empty")
86             .endControlFlow()
87             .build(),
88         )
89         .build(),
90     )
91     assertThat(source.toString()).isEqualTo(
92       """
93         |package com.squareup.tacos
94         |
95         |import kotlin.String
96         |
97         |public class Taco(
98         |  cheese: String,
99         |) {
100         |  init {
101         |    require(cheese.isNotEmpty()) {
102         |      "cheese cannot be empty"
103         |    }
104         |  }
105         |}
106         |
107       """.trimMargin(),
108     )
109   }
110 
primaryConstructorPropertiesnull111   @Test fun primaryConstructorProperties() {
112     val source = FileSpec.get(
113       tacosPackage,
114       TypeSpec.classBuilder("Taco")
115         .primaryConstructor(
116           FunSpec.constructorBuilder()
117             .addParameter("cheese", String::class)
118             .addParameter("cilantro", String::class)
119             .addParameter("lettuce", String::class)
120             .beginControlFlow("require(!cheese.isEmpty())")
121             .addStatement("%S", "cheese cannot be empty")
122             .endControlFlow()
123             .build(),
124         )
125         .addProperty(
126           PropertySpec.builder("cheese", String::class)
127             .initializer("cheese")
128             .build(),
129         )
130         .addProperty(
131           PropertySpec.builder("cilantro", String::class.asTypeName())
132             .mutable()
133             .initializer("cilantro")
134             .build(),
135         )
136         .addProperty(
137           PropertySpec.builder("lettuce", String::class)
138             .initializer("lettuce.trim()")
139             .build(),
140         )
141         .addProperty(
142           PropertySpec.builder("onion", Boolean::class)
143             .initializer("true")
144             .build(),
145         )
146         .build(),
147     )
148     assertThat(source.toString()).isEqualTo(
149       """
150         |package com.squareup.tacos
151         |
152         |import kotlin.Boolean
153         |import kotlin.String
154         |
155         |public class Taco(
156         |  public val cheese: String,
157         |  public var cilantro: String,
158         |  lettuce: String,
159         |) {
160         |  public val lettuce: String = lettuce.trim()
161         |
162         |  public val onion: Boolean = true
163         |  init {
164         |    require(!cheese.isEmpty()) {
165         |      "cheese cannot be empty"
166         |    }
167         |  }
168         |}
169         |
170       """.trimMargin(),
171     )
172   }
173 
propertyModifiersnull174   @Test fun propertyModifiers() {
175     val source = FileSpec.get(
176       tacosPackage,
177       TypeSpec.classBuilder("Taco")
178         .addProperty(
179           PropertySpec.builder("CHEESE", String::class, KModifier.PRIVATE, KModifier.CONST)
180             .initializer("%S", "monterey jack")
181             .build(),
182         )
183         .addProperty(
184           PropertySpec.builder("sauce", String::class.asTypeName(), KModifier.PUBLIC)
185             .mutable()
186             .initializer("%S", "chipotle mayo")
187             .build(),
188         )
189         .build(),
190     )
191     assertThat(source.toString()).isEqualTo(
192       """
193         |package com.squareup.tacos
194         |
195         |import kotlin.String
196         |
197         |public class Taco {
198         |  private const val CHEESE: String = "monterey jack"
199         |
200         |  public var sauce: String = "chipotle mayo"
201         |}
202         |
203       """.trimMargin(),
204     )
205   }
206 
mistargetedModifiernull207   @Test fun mistargetedModifier() {
208     assertThrows<IllegalArgumentException> {
209       PropertySpec.builder("CHEESE", String::class, KModifier.DATA).build()
210     }
211   }
212 
visibilityModifiersnull213   @Test fun visibilityModifiers() {
214     val source = FileSpec.get(
215       tacosPackage,
216       TypeSpec.classBuilder("Taco")
217         .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build())
218         .addFunction(FunSpec.builder("b").addModifiers(KModifier.PROTECTED).build())
219         .addFunction(FunSpec.builder("c").addModifiers(KModifier.INTERNAL).build())
220         .addFunction(FunSpec.builder("d").addModifiers(KModifier.PRIVATE).build())
221         .build(),
222     )
223     assertThat(source.toString()).isEqualTo(
224       """
225         |package com.squareup.tacos
226         |
227         |import kotlin.Unit
228         |
229         |public class Taco {
230         |  public fun a(): Unit {
231         |  }
232         |
233         |  protected fun b(): Unit {
234         |  }
235         |
236         |  internal fun c(): Unit {
237         |  }
238         |
239         |  private fun d(): Unit {
240         |  }
241         |}
242         |
243       """.trimMargin(),
244     )
245   }
246 
stringsnull247   @Test fun strings() {
248     val source = FileSpec.get(
249       tacosPackage,
250       TypeSpec.classBuilder("Taco")
251         .addFunction(
252           FunSpec.builder("strings")
253             .addStatement("val a = %S", "basic string")
254             .addStatement("val b = %S", "string with a \$ dollar sign")
255             .build(),
256         )
257         .build(),
258     )
259     assertThat(source.toString()).isEqualTo(
260       "" +
261         "package com.squareup.tacos\n" +
262         "\n" +
263         "import kotlin.Unit\n" +
264         "\n" +
265         "public class Taco {\n" +
266         "  public fun strings(): Unit {\n" +
267         "    val a = \"basic string\"\n" +
268         "    val b = \"string with a \${\'\$\'} dollar sign\"\n" +
269         "  }\n" +
270         "}\n",
271     )
272   }
273 
274   /** When emitting a triple quote, KotlinPoet escapes the 3rd quote in the triplet. */
rawStringsnull275   @Test fun rawStrings() {
276     val source = FileSpec.get(
277       tacosPackage,
278       TypeSpec.classBuilder("Taco")
279         .addFunction(
280           FunSpec.builder("strings")
281             .addStatement("val a = %S", "\"\n")
282             .addStatement("val b = %S", "a\"\"\"b\"\"\"\"\"\"c\n")
283             .addStatement(
284               "val c = %S",
285               """
286             |whoa
287             |"raw"
288             |string
289               """.trimMargin(),
290             )
291             .addStatement(
292               "val d = %S",
293               """
294             |"raw"
295             |string
296             |with
297             |${'$'}a interpolated value
298               """.trimMargin(),
299             )
300             .build(),
301         )
302         .build(),
303     )
304     assertThat(source.toString()).isEqualTo(
305       "" +
306         "package com.squareup.tacos\n" +
307         "\n" +
308         "import kotlin.Unit\n" +
309         "\n" +
310         "public class Taco {\n" +
311         "  public fun strings(): Unit {\n" +
312         "    val a = \"\"\"\n" +
313         "        |\"\n" +
314         "        |\"\"\".trimMargin()\n" +
315         "    val b = \"\"\"\n" +
316         "        |a\"\"\${'\"'}b\"\"\${'\"'}\"\"\${'\"'}c\n" +
317         "        |\"\"\".trimMargin()\n" +
318         "    val c = \"\"\"\n" +
319         "        |whoa\n" +
320         "        |\"raw\"\n" +
321         "        |string\n" +
322         "        \"\"\".trimMargin()\n" +
323         "    val d = \"\"\"\n" +
324         "        |\"raw\"\n" +
325         "        |string\n" +
326         "        |with\n" +
327         "        |\${\'\$\'}a interpolated value\n" +
328         "        \"\"\".trimMargin()\n" +
329         "  }\n" +
330         "}\n",
331     )
332   }
333 
334   /**
335    * When a string literal ends in a newline, there's a pipe `|` immediately preceding the closing
336    * triple quote. Otherwise the closing triple quote has no preceding `|`.
337    */
edgeCaseStringsnull338   @Test fun edgeCaseStrings() {
339     val source = FileSpec.get(
340       tacosPackage,
341       TypeSpec.classBuilder("Taco")
342         .addFunction(
343           FunSpec.builder("strings")
344             .addStatement("val a = %S", "\n")
345             .addStatement("val b = %S", " \n ")
346             .build(),
347         )
348         .build(),
349     )
350     assertThat(source.toString()).isEqualTo(
351       "" +
352         "package com.squareup.tacos\n" +
353         "\n" +
354         "import kotlin.Unit\n" +
355         "\n" +
356         "public class Taco {\n" +
357         "  public fun strings(): Unit {\n" +
358         "    val a = \"\"\"\n" +
359         "        |\n" +
360         "        |\"\"\".trimMargin()\n" +
361         "    val b = \"\"\"\n" +
362         "        | \n" +
363         "        | \n" +
364         "        \"\"\".trimMargin()\n" +
365         "  }\n" +
366         "}\n",
367     )
368   }
369 
parameterDefaultValuenull370   @Test fun parameterDefaultValue() {
371     val source = FileSpec.get(
372       tacosPackage,
373       TypeSpec.classBuilder("Taco")
374         .addFunction(
375           FunSpec.builder("addCheese")
376             .addParameter(
377               ParameterSpec.builder("kind", String::class)
378                 .defaultValue("%S", "monterey jack")
379                 .build(),
380             )
381             .build(),
382         )
383         .build(),
384     )
385     assertThat(source.toString()).isEqualTo(
386       """
387         |package com.squareup.tacos
388         |
389         |import kotlin.String
390         |import kotlin.Unit
391         |
392         |public class Taco {
393         |  public fun addCheese(kind: String = "monterey jack"): Unit {
394         |  }
395         |}
396         |
397       """.trimMargin(),
398     )
399   }
400 
extensionFunctionnull401   @Test fun extensionFunction() {
402     val source = FileSpec.builder(tacosPackage, "Taco")
403       .addFunction(
404         FunSpec.builder("shrink")
405           .returns(String::class)
406           .receiver(String::class)
407           .addStatement("return substring(0, length - 1)")
408           .build(),
409       )
410       .build()
411     assertThat(source.toString()).isEqualTo(
412       """
413         |package com.squareup.tacos
414         |
415         |import kotlin.String
416         |
417         |public fun String.shrink(): String = substring(0, length - 1)
418         |
419       """.trimMargin(),
420     )
421   }
422 
extensionFunctionLambdanull423   @Test fun extensionFunctionLambda() {
424     val source = FileSpec.builder(tacosPackage, "Taco")
425       .addFunction(
426         FunSpec.builder("shrink")
427           .returns(String::class)
428           .receiver(
429             LambdaTypeName.get(
430               parameters = arrayOf(String::class.asClassName()),
431               returnType = String::class.asTypeName(),
432             ),
433           )
434           .addStatement("return substring(0, length - 1)")
435           .build(),
436       )
437       .build()
438     assertThat(source.toString()).isEqualTo(
439       """
440         |package com.squareup.tacos
441         |
442         |import kotlin.String
443         |
444         |public fun ((String) -> String).shrink(): String = substring(0, length - 1)
445         |
446       """.trimMargin(),
447     )
448   }
449 
extensionFunctionLambdaWithParamNamenull450   @Test fun extensionFunctionLambdaWithParamName() {
451     val source = FileSpec.builder(tacosPackage, "Taco")
452       .addFunction(
453         FunSpec.builder("whatever")
454           .returns(Unit::class)
455           .receiver(
456             LambdaTypeName.get(
457               parameters = arrayOf(ParameterSpec.builder("name", String::class).build()),
458               returnType = Unit::class.asClassName(),
459             ),
460           )
461           .addStatement("return Unit")
462           .build(),
463       )
464       .build()
465     assertThat(source.toString()).isEqualTo(
466       """
467       |package com.squareup.tacos
468       |
469       |import kotlin.String
470       |import kotlin.Unit
471       |
472       |public fun ((name: String) -> Unit).whatever(): Unit = Unit
473       |
474       """.trimMargin(),
475     )
476   }
477 
extensionFunctionLambdaWithMultipleParamsnull478   @Test fun extensionFunctionLambdaWithMultipleParams() {
479     val source = FileSpec.builder(tacosPackage, "Taco")
480       .addFunction(
481         FunSpec.builder("whatever")
482           .returns(Unit::class)
483           .receiver(
484             LambdaTypeName.get(
485               parameters = listOf(
486                 ParameterSpec.builder("name", String::class).build(),
487                 ParameterSpec.unnamed(Int::class),
488                 ParameterSpec.builder("age", Long::class).build(),
489               ),
490               returnType = Unit::class.asClassName(),
491             ),
492           )
493           .addStatement("return Unit")
494           .build(),
495       )
496       .build()
497     assertThat(source.toString()).isEqualTo(
498       """
499       |package com.squareup.tacos
500       |
501       |import kotlin.Int
502       |import kotlin.Long
503       |import kotlin.String
504       |import kotlin.Unit
505       |
506       |public fun ((
507       |  name: String,
508       |  Int,
509       |  age: Long,
510       |) -> Unit).whatever(): Unit = Unit
511       |
512       """.trimMargin(),
513     )
514   }
515 
extensionPropertynull516   @Test fun extensionProperty() {
517     val source = FileSpec.builder(tacosPackage, "Taco")
518       .addProperty(
519         PropertySpec.builder("extensionProperty", Int::class)
520           .receiver(String::class)
521           .getter(
522             FunSpec.getterBuilder()
523               .addStatement("return length")
524               .build(),
525           )
526           .build(),
527       )
528       .build()
529     assertThat(source.toString()).isEqualTo(
530       """
531         |package com.squareup.tacos
532         |
533         |import kotlin.Int
534         |import kotlin.String
535         |
536         |public val String.extensionProperty: Int
537         |  get() = length
538         |
539       """.trimMargin(),
540     )
541   }
542 
extensionPropertyLambdanull543   @Test fun extensionPropertyLambda() {
544     val source = FileSpec.builder(tacosPackage, "Taco")
545       .addProperty(
546         PropertySpec.builder("extensionProperty", Int::class)
547           .receiver(
548             LambdaTypeName.get(
549               parameters = arrayOf(String::class.asClassName()),
550               returnType = String::class.asClassName(),
551             ),
552           )
553           .getter(
554             FunSpec.getterBuilder()
555               .addStatement("return length")
556               .build(),
557           )
558           .build(),
559       )
560       .build()
561     assertThat(source.toString()).isEqualTo(
562       """
563         |package com.squareup.tacos
564         |
565         |import kotlin.Int
566         |import kotlin.String
567         |
568         |public val ((String) -> String).extensionProperty: Int
569         |  get() = length
570         |
571       """.trimMargin(),
572     )
573   }
574 
nullableTypesnull575   @Test fun nullableTypes() {
576     val list = (List::class.asClassName().copy(nullable = true) as ClassName)
577       .parameterizedBy(Int::class.asClassName().copy(nullable = true))
578       .copy(nullable = true)
579     assertThat(list.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?")
580   }
581 
getAndSetnull582   @Test fun getAndSet() {
583     val source = FileSpec.builder(tacosPackage, "Taco")
584       .addProperty(
585         PropertySpec.builder("propertyWithCustomAccessors", Int::class.asTypeName())
586           .mutable()
587           .initializer("%L", 1)
588           .getter(
589             FunSpec.getterBuilder()
590               .addStatement("println(%S)", "getter")
591               .addStatement("return field")
592               .build(),
593           )
594           .setter(
595             FunSpec.setterBuilder()
596               .addParameter("value", Int::class)
597               .addStatement("println(%S)", "setter")
598               .addStatement("field = value")
599               .build(),
600           )
601           .build(),
602       )
603       .build()
604     assertThat(source.toString()).isEqualTo(
605       """
606         |package com.squareup.tacos
607         |
608         |import kotlin.Int
609         |
610         |public var propertyWithCustomAccessors: Int = 1
611         |  get() {
612         |    println("getter")
613         |    return field
614         |  }
615         |  set(`value`) {
616         |    println("setter")
617         |    field = value
618         |  }
619         |
620       """.trimMargin(),
621     )
622   }
623 
propertyWithLongInitializerWrappingnull624   @Test fun propertyWithLongInitializerWrapping() {
625     val source = FileSpec.builder(tacosPackage, "Taco")
626       .addProperty(
627         PropertySpec
628           .builder("foo", ClassName(tacosPackage, "Foo").copy(nullable = true))
629           .addModifiers(KModifier.PRIVATE)
630           .initializer("DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)")
631           .build(),
632       )
633       .build()
634     assertThat(source.toString()).isEqualTo(
635       """
636       |package com.squareup.tacos
637       |
638       |private val foo: Foo? =
639       |    DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)
640       |
641       """.trimMargin(),
642     )
643   }
644 
stackedPropertyModifiersnull645   @Test fun stackedPropertyModifiers() {
646     val source = FileSpec.builder(tacosPackage, "Taco")
647       .addType(
648         TypeSpec.classBuilder("A")
649           .addModifiers(KModifier.ABSTRACT)
650           .addProperty(
651             PropertySpec.builder("q", String::class.asTypeName())
652               .mutable()
653               .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
654               .build(),
655           )
656           .build(),
657       )
658       .addProperty(
659         PropertySpec.builder("p", String::class)
660           .addModifiers(KModifier.CONST, KModifier.INTERNAL)
661           .initializer("%S", "a")
662           .build(),
663       )
664       .addType(
665         TypeSpec.classBuilder("B")
666           .superclass(ClassName(tacosPackage, "A"))
667           .addModifiers(KModifier.ABSTRACT)
668           .addProperty(
669             PropertySpec.builder("q", String::class.asTypeName())
670               .mutable()
671               .addModifiers(
672                 KModifier.FINAL,
673                 KModifier.LATEINIT,
674                 KModifier.OVERRIDE,
675                 KModifier.PUBLIC,
676               )
677               .build(),
678           )
679           .build(),
680       )
681       .build()
682     assertThat(source.toString()).isEqualTo(
683       """
684         |package com.squareup.tacos
685         |
686         |import kotlin.String
687         |
688         |public abstract class A {
689         |  protected abstract var q: String
690         |}
691         |
692         |internal const val p: String = "a"
693         |
694         |public abstract class B : A() {
695         |  public final override lateinit var q: String
696         |}
697         |
698       """.trimMargin(),
699     )
700   }
701 
stackedFunModifiersnull702   @Test fun stackedFunModifiers() {
703     val source = FileSpec.get(
704       tacosPackage,
705       TypeSpec.classBuilder("A")
706         .addModifiers(KModifier.OPEN)
707         .addFunction(
708           FunSpec.builder("get")
709             .addModifiers(
710               KModifier.EXTERNAL,
711               KModifier.INFIX,
712               KModifier.OPEN,
713               KModifier.OPERATOR,
714               KModifier.PROTECTED,
715             )
716             .addParameter("v", String::class)
717             .returns(String::class)
718             .build(),
719         )
720         .addFunction(
721           FunSpec.builder("loop")
722             .addModifiers(KModifier.FINAL, KModifier.INLINE, KModifier.INTERNAL, KModifier.TAILREC)
723             .returns(String::class)
724             .addStatement("return %S", "a")
725             .build(),
726         )
727         .build(),
728     )
729     assertThat(source.toString()).isEqualTo(
730       """
731         |package com.squareup.tacos
732         |
733         |import kotlin.String
734         |
735         |public open class A {
736         |  protected open external infix operator fun `get`(v: String): String
737         |
738         |  internal final tailrec inline fun loop(): String = "a"
739         |}
740         |
741       """.trimMargin(),
742     )
743   }
744 
basicExpressionBodynull745   @Test fun basicExpressionBody() {
746     val source = FileSpec.builder(tacosPackage, "Taco")
747       .addFunction(
748         FunSpec.builder("addA")
749           .addParameter("s", String::class)
750           .returns(String::class)
751           .addStatement("return s + %S", "a")
752           .build(),
753       )
754       .build()
755     assertThat(source.toString()).isEqualTo(
756       """
757         |package com.squareup.tacos
758         |
759         |import kotlin.String
760         |
761         |public fun addA(s: String): String = s + "a"
762         |
763       """.trimMargin(),
764     )
765   }
766 
suspendingLambdasnull767   @Test fun suspendingLambdas() {
768     val barType = ClassName(tacosPackage, "Bar")
769     val suspendingLambda = LambdaTypeName
770       .get(parameters = arrayOf(ClassName(tacosPackage, "Foo")), returnType = barType)
771       .copy(suspending = true)
772     val source = FileSpec.builder(tacosPackage, "Taco")
773       .addProperty(
774         PropertySpec.builder("bar", suspendingLambda)
775           .mutable()
776           .initializer("{ %T() }", barType)
777           .build(),
778       )
779       .addProperty(
780         PropertySpec.builder("nullBar", suspendingLambda.copy(nullable = true))
781           .mutable()
782           .initializer("null")
783           .build(),
784       )
785       .addFunction(
786         FunSpec.builder("foo")
787           .addParameter("bar", suspendingLambda)
788           .build(),
789       )
790       .build()
791     assertThat(source.toString()).isEqualTo(
792       """
793       |package com.squareup.tacos
794       |
795       |import kotlin.Unit
796       |
797       |public var bar: suspend (Foo) -> Bar = { Bar() }
798       |
799       |public var nullBar: (suspend (Foo) -> Bar)? = null
800       |
801       |public fun foo(bar: suspend (Foo) -> Bar): Unit {
802       |}
803       |
804       """.trimMargin(),
805     )
806   }
807 
enumAsDefaultArgumentnull808   @Test fun enumAsDefaultArgument() {
809     val source = FileSpec.builder(tacosPackage, "Taco")
810       .addFunction(
811         FunSpec.builder("timeout")
812           .addParameter("duration", Long::class)
813           .addParameter(
814             ParameterSpec.builder("timeUnit", TimeUnit::class)
815               .defaultValue("%T.%L", TimeUnit::class, TimeUnit.MILLISECONDS.name)
816               .build(),
817           )
818           .addStatement("this.timeout = timeUnit.toMillis(duration)")
819           .build(),
820       )
821       .build()
822     assertThat(source.toString()).isEqualTo(
823       """
824       |package com.squareup.tacos
825       |
826       |import java.util.concurrent.TimeUnit
827       |import kotlin.Long
828       |import kotlin.Unit
829       |
830       |public fun timeout(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): Unit {
831       |  this.timeout = timeUnit.toMillis(duration)
832       |}
833       |
834       """.trimMargin(),
835     )
836   }
837 
dynamicTypenull838   @Test fun dynamicType() {
839     val source = FileSpec.builder(tacosPackage, "Taco")
840       .addFunction(
841         FunSpec.builder("dynamicTest")
842           .addCode(
843             CodeBlock.of(
844               "%L",
845               PropertySpec.builder("d1", DYNAMIC)
846                 .initializer("%S", "Taco")
847                 .build(),
848             ),
849           )
850           .addCode(
851             CodeBlock.of(
852               "%L",
853               PropertySpec.builder("d2", DYNAMIC)
854                 .initializer("1f")
855                 .build(),
856             ),
857           )
858           .addStatement("// dynamics are dangerous!")
859           .addStatement("println(d1 - d2)")
860           .build(),
861       )
862       .build()
863     assertThat(source.toString()).isEqualTo(
864       """
865       |package com.squareup.tacos
866       |
867       |import kotlin.Unit
868       |
869       |public fun dynamicTest(): Unit {
870       |  val d1: dynamic = "Taco"
871       |  val d2: dynamic = 1f
872       |  // dynamics are dangerous!
873       |  println(d1 - d2)
874       |}
875       |
876       """.trimMargin(),
877     )
878   }
879 
primaryConstructorParameterAnnotationnull880   @Test fun primaryConstructorParameterAnnotation() {
881     val file = FileSpec.builder("com.squareup.tacos", "Taco")
882       .addType(
883         TypeSpec.classBuilder("Taco")
884           .primaryConstructor(
885             FunSpec.constructorBuilder()
886               .addParameter("foo", String::class)
887               .build(),
888           )
889           .addProperty(
890             PropertySpec.builder("foo", String::class)
891               .jvmField()
892               .initializer("foo")
893               .build(),
894           )
895           .build(),
896       )
897       .build()
898     assertThat(file.toString()).isEqualTo(
899       """
900       |package com.squareup.tacos
901       |
902       |import kotlin.String
903       |import kotlin.jvm.JvmField
904       |
905       |public class Taco(
906       |  @JvmField
907       |  public val foo: String,
908       |)
909       |
910       """.trimMargin(),
911     )
912   }
913 
914   // https://github.com/square/kotlinpoet/issues/346
importTypeArgumentInParameterizedTypeNamenull915   @Test fun importTypeArgumentInParameterizedTypeName() {
916     val file = FileSpec.builder("com.squareup.tacos", "Taco")
917       .addFunction(
918         FunSpec.builder("foo")
919           .addParameter(
920             "a",
921             List::class.asTypeName()
922               .parameterizedBy(Int::class.asTypeName().jvmSuppressWildcards()),
923           )
924           .build(),
925       )
926       .build()
927     assertThat(file.toString()).isEqualTo(
928       """
929       |package com.squareup.tacos
930       |
931       |import kotlin.Int
932       |import kotlin.Unit
933       |import kotlin.collections.List
934       |import kotlin.jvm.JvmSuppressWildcards
935       |
936       |public fun foo(a: List<@JvmSuppressWildcards Int>): Unit {
937       |}
938       |
939       """.trimMargin(),
940     )
941   }
942 
943   // https://github.com/square/kotlinpoet/issues/462
foldingPropertyWithLambdaInitializernull944   @Test fun foldingPropertyWithLambdaInitializer() {
945     val param = ParameterSpec.builder("arg", ANY).build()
946     val initializer = CodeBlock.builder()
947       .beginControlFlow("{ %L ->", param)
948       .addStatement("println(\"arg=\$%N\")", param)
949       .endControlFlow()
950       .build()
951     val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
952     val property = PropertySpec.builder("foo", lambdaTypeName)
953       .initializer("foo")
954       .build()
955     val file = FileSpec.builder("com.squareup.tacos", "Taco")
956       .addType(
957         TypeSpec.classBuilder("Taco")
958           .primaryConstructor(
959             FunSpec.constructorBuilder()
960               .addParameter(
961                 ParameterSpec.builder("foo", lambdaTypeName)
962                   .defaultValue(initializer)
963                   .build(),
964               )
965               .build(),
966           )
967           .addProperty(property)
968           .build(),
969       )
970       .build()
971     assertThat(file.toString()).isEqualTo(
972       """
973       |package com.squareup.tacos
974       |
975       |import com.example.SomeTypeAlias
976       |
977       |public class Taco(
978       |  public val foo: SomeTypeAlias = { arg: kotlin.Any ->
979       |    println("arg=${'$'}arg")
980       |  }
981       |  ,
982       |)
983       |
984       """.trimMargin(),
985     )
986   }
987 
988   // https://github.com/square/kotlinpoet/issues/483
foldingPropertyWithEscapedNamenull989   @Test fun foldingPropertyWithEscapedName() {
990     val file = FileSpec.builder("com.squareup.tacos", "AlarmInfo")
991       .addType(
992         TypeSpec.classBuilder("AlarmInfo")
993           .primaryConstructor(
994             FunSpec.constructorBuilder()
995               .addParameter("when", Float::class)
996               .build(),
997           )
998           .addProperty(
999             PropertySpec.builder("when", Float::class)
1000               .initializer("when")
1001               .build(),
1002           )
1003           .build(),
1004       )
1005       .build()
1006     assertThat(file.toString()).isEqualTo(
1007       """
1008       |package com.squareup.tacos
1009       |
1010       |import kotlin.Float
1011       |
1012       |public class AlarmInfo(
1013       |  public val `when`: Float,
1014       |)
1015       |
1016       """.trimMargin(),
1017     )
1018   }
1019 
1020   // https://github.com/square/kotlinpoet/issues/577
noWrappingBetweenParamNameAndTypenull1021   @Test fun noWrappingBetweenParamNameAndType() {
1022     val file = FileSpec.builder("com.squareup.tacos", "Taco")
1023       .addFunction(
1024         FunSpec.builder("functionWithAPrettyLongNameThatWouldCauseWrapping")
1025           .addParameter("parameterWithALongNameThatWouldAlsoCauseWrapping", String::class)
1026           .build(),
1027       )
1028       .build()
1029     assertThat(file.toString()).isEqualTo(
1030       """
1031       |package com.squareup.tacos
1032       |
1033       |import kotlin.String
1034       |import kotlin.Unit
1035       |
1036       |public
1037       |    fun functionWithAPrettyLongNameThatWouldCauseWrapping(parameterWithALongNameThatWouldAlsoCauseWrapping: String):
1038       |    Unit {
1039       |}
1040       |
1041       """.trimMargin(),
1042     )
1043   }
1044 
1045   // https://github.com/square/kotlinpoet/issues/576
noWrappingBetweenValAndPropertyNamenull1046   @Test fun noWrappingBetweenValAndPropertyName() {
1047     val wireField = ClassName("com.squareup.wire", "WireField")
1048     val file = FileSpec.builder("com.squareup.tacos", "Taco")
1049       .addType(
1050         TypeSpec.classBuilder("Taco")
1051           .addModifiers(KModifier.DATA)
1052           .addProperty(
1053             PropertySpec.builder("name", String::class)
1054               .addAnnotation(
1055                 AnnotationSpec.builder(wireField)
1056                   .addMember("tag = %L", 1)
1057                   .addMember("adapter = %S", "CustomStringAdapterWithALongNameThatCauses")
1058                   .build(),
1059               )
1060               .initializer("name")
1061               .build(),
1062           )
1063           .primaryConstructor(
1064             FunSpec.constructorBuilder()
1065               .addParameter(
1066                 ParameterSpec.builder("name", String::class)
1067                   .build(),
1068               )
1069               .build(),
1070           )
1071           .build(),
1072       )
1073       .build()
1074     assertThat(file.toString()).isEqualTo(
1075       """
1076       |package com.squareup.tacos
1077       |
1078       |import com.squareup.wire.WireField
1079       |import kotlin.String
1080       |
1081       |public data class Taco(
1082       |  @WireField(
1083       |    tag = 1,
1084       |    adapter = "CustomStringAdapterWithALongNameThatCauses",
1085       |  )
1086       |  public val name: String,
1087       |)
1088       |
1089       """.trimMargin(),
1090     )
1091   }
1092 
1093   // https://github.com/square/kotlinpoet/issues/578
wrappingInsideKdocKeepsKdocFormattingnull1094   @Test fun wrappingInsideKdocKeepsKdocFormatting() {
1095     val file = FileSpec.builder("com.squareup.tacos", "Taco")
1096       .addType(
1097         TypeSpec.classBuilder("Builder")
1098           .addKdoc(
1099             "Builder class for Foo. Allows creating instances of Foo by initializing " +
1100               "a subset of their fields, following the Builder pattern.\n",
1101           )
1102           .addFunction(
1103             FunSpec.builder("summary_text")
1104               .addKdoc(
1105                 "The description for the choice, e.g. \"Currently unavailable due to " +
1106                   "high demand. Please try later.\" May be null.",
1107               )
1108               .addParameter("summary_text", String::class.asClassName().copy(nullable = true))
1109               .returns(ClassName("com.squareup.tacos", "Builder"))
1110               .addStatement("this.summary_text = summary_text")
1111               .addStatement("return this")
1112               .build(),
1113           )
1114           .build(),
1115       )
1116       .build()
1117     assertThat(file.toString()).isEqualTo(
1118       """
1119       |package com.squareup.tacos
1120       |
1121       |import kotlin.String
1122       |
1123       |/**
1124       | * Builder class for Foo. Allows creating instances of Foo by initializing a subset of their fields,
1125       | * following the Builder pattern.
1126       | */
1127       |public class Builder {
1128       |  /**
1129       |   * The description for the choice, e.g. "Currently unavailable due to high demand. Please try
1130       |   * later." May be null.
1131       |   */
1132       |  public fun summary_text(summary_text: String?): Builder {
1133       |    this.summary_text = summary_text
1134       |    return this
1135       |  }
1136       |}
1137       |
1138       """.trimMargin(),
1139     )
1140   }
1141 
1142   // https://github.com/square/kotlinpoet/issues/606
typeNamesInsideTemplateStringsGetImportednull1143   @Test fun typeNamesInsideTemplateStringsGetImported() {
1144     val taco = ClassName("com.squareup.tacos", "Taco")
1145     val file = FileSpec.builder("com.squareup.example", "Tacos")
1146       .addFunction(
1147         FunSpec.builder("main")
1148           .addStatement("println(%P)", CodeBlock.of("Here's a taco: \${%T()}", taco))
1149           .build(),
1150       )
1151       .build()
1152     assertThat(file.toString()).isEqualTo(
1153       """
1154       |package com.squareup.example
1155       |
1156       |import com.squareup.tacos.Taco
1157       |import kotlin.Unit
1158       |
1159       |public fun main(): Unit {
1160       |  println(${'"'}""Here's a taco: ${'$'}{Taco()}""${'"'})
1161       |}
1162       |
1163       """.trimMargin(),
1164     )
1165   }
1166 
1167   // https://github.com/square/kotlinpoet/issues/606
memberNamesInsideTemplateStringsGetImportednull1168   @Test fun memberNamesInsideTemplateStringsGetImported() {
1169     val contentToString = MemberName("kotlin.collections", "contentToString")
1170     val file = FileSpec.builder("com.squareup.example", "Tacos")
1171       .addFunction(
1172         FunSpec.builder("main")
1173           .addStatement("val ints = arrayOf(1, 2, 3)")
1174           .addStatement("println(%P)", CodeBlock.of("\${ints.%M()}", contentToString))
1175           .build(),
1176       )
1177       .build()
1178     assertThat(file.toString()).isEqualTo(
1179       """
1180       |package com.squareup.example
1181       |
1182       |import kotlin.Unit
1183       |import kotlin.collections.contentToString
1184       |
1185       |public fun main(): Unit {
1186       |  val ints = arrayOf(1, 2, 3)
1187       |  println(${'"'}""${'$'}{ints.contentToString()}""${'"'})
1188       |}
1189       |
1190       """.trimMargin(),
1191     )
1192   }
1193 
1194   // https://github.com/square/kotlinpoet/issues/701
noIllegalCharacterInIdentifiernull1195   @Test fun noIllegalCharacterInIdentifier() {
1196     assertThrows<IllegalArgumentException> {
1197       TypeSpec.enumBuilder("MyEnum")
1198         .addEnumConstant("with.dots") // dots are illegal, so this should fail
1199         .build().toString()
1200     }.hasMessageThat().isEqualTo("Can't escape identifier `with.dots` because it contains illegal characters: .")
1201   }
1202 
1203   // https://github.com/square/kotlinpoet/issues/814
percentAtTheEndOfKdocnull1204   @Test fun percentAtTheEndOfKdoc() {
1205     val paramSpec1 = ParameterSpec.builder("a", Int::class)
1206       .addKdoc("Progress in %%")
1207       .build()
1208     val paramSpec2 = ParameterSpec.builder("b", Int::class)
1209       .addKdoc("Some other parameter with %%")
1210       .build()
1211     val funSpec = FunSpec.builder("test")
1212       .addParameters(listOf(paramSpec1, paramSpec2))
1213       .build()
1214     assertThat(funSpec.toString()).isEqualTo(
1215       """
1216       |/**
1217       | * @param a Progress in %
1218       | * @param b Some other parameter with %
1219       | */
1220       |public fun test(a: kotlin.Int, b: kotlin.Int): kotlin.Unit {
1221       |}
1222       |
1223       """.trimMargin(),
1224     )
1225   }
1226 
1227   // https://github.com/square/kotlinpoet/issues/1031
superClassGetsFullyQualifiedOnConflictnull1228   @Test fun superClassGetsFullyQualifiedOnConflict() {
1229     val namespace = "test"
1230 
1231     val kotlinExceptionName = ClassName("kotlin", "Exception")
1232     val customExceptionName = ClassName(namespace, "Exception")
1233     val customException = TypeSpec
1234       .classBuilder("Exception")
1235       .superclass(kotlinExceptionName)
1236       .addFunction(
1237         FunSpec
1238           .builder("test")
1239           .addParameter("e", customExceptionName)
1240           .build(),
1241       )
1242       .build()
1243 
1244     val file = FileSpec.builder(namespace, "Exception")
1245       .addType(customException)
1246       .build()
1247     assertThat(file.toString()).isEqualTo(
1248       """
1249       |package test
1250       |
1251       |import kotlin.Unit
1252       |
1253       |public class Exception : kotlin.Exception() {
1254       |  public fun test(e: Exception): Unit {
1255       |  }
1256       |}
1257       |
1258       """.trimMargin(),
1259     )
1260   }
1261 
allStringsAreUnderscorenull1262   @Test fun allStringsAreUnderscore() {
1263     val file = FileSpec.builder("com.squareup.tacos", "SourceWithUnderscores")
1264       .addType(
1265         TypeSpec.classBuilder("SourceWithUnderscores")
1266           .primaryConstructor(
1267             FunSpec.constructorBuilder()
1268               .addParameter("_", Float::class)
1269               .addParameter("____", Float::class)
1270               .build(),
1271           )
1272           .addProperty(
1273             PropertySpec.builder("_", Float::class)
1274               .initializer("_")
1275               .build(),
1276           )
1277           .addProperty(
1278             PropertySpec.builder("____", Float::class)
1279               .initializer("____")
1280               .build(),
1281           )
1282           .build(),
1283       )
1284       .build()
1285     assertThat(file.toString()).isEqualTo(
1286       """
1287       |package com.squareup.tacos
1288       |
1289       |import kotlin.Float
1290       |
1291       |public class SourceWithUnderscores(
1292       |  public val `_`: Float,
1293       |  public val `____`: Float,
1294       |)
1295       |
1296       """.trimMargin(),
1297     )
1298   }
1299 
generatedImportAliasesnull1300   @Test fun generatedImportAliases() {
1301     val squareTaco = ClassName("com.squareup.tacos", "Taco")
1302     val blockTaco = ClassName("xyz.block.tacos", "Taco")
1303     val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty")
1304     val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty")
1305     val file = FileSpec.builder("com.example", "Test")
1306       .addFunction(
1307         FunSpec.builder("main")
1308           .addStatement("val squareTaco = %L", squareTaco.constructorReference())
1309           .addStatement("val blockTaco = %L", blockTaco.constructorReference())
1310           .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty)
1311           .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty)
1312           .build(),
1313       )
1314       .build()
1315     assertThat(file.toString()).isEqualTo(
1316       """
1317       |package com.example
1318       |
1319       |import kotlin.Unit
1320       |import com.squareup.cash.util.isNullOrEmpty as utilIsNullOrEmpty
1321       |import com.squareup.tacos.Taco as SquareupTacosTaco
1322       |import kotlin.text.isNullOrEmpty as textIsNullOrEmpty
1323       |import xyz.block.tacos.Taco as BlockTacosTaco
1324       |
1325       |public fun main(): Unit {
1326       |  val squareTaco = ::SquareupTacosTaco
1327       |  val blockTaco = ::BlockTacosTaco
1328       |  val isSquareTacoNull = "Taco".textIsNullOrEmpty()
1329       |  val isBlockTacoNull = "Taco".utilIsNullOrEmpty()
1330       |}
1331       |
1332       """.trimMargin(),
1333     )
1334   }
1335 
memberImportsOverGeneratedImportAliasesnull1336   @Test fun memberImportsOverGeneratedImportAliases() {
1337     val squareTaco = ClassName("com.squareup.tacos", "Taco")
1338     val blockTaco = ClassName("xyz.block.tacos", "Taco")
1339     val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty")
1340     val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty")
1341     val file = FileSpec.builder("com.example", "Test")
1342       .addAliasedImport(squareTaco, "SquareTaco")
1343       .addAliasedImport(blockTaco, "BlockTaco")
1344       .addAliasedImport(kotlinIsNullOrEmpty, "kotlinIsNullOrEmpty")
1345       .addAliasedImport(cashIsNullOrEmpty, "cashIsNullOrEmpty")
1346       .addFunction(
1347         FunSpec.builder("main")
1348           .addStatement("val squareTaco = %L", squareTaco.constructorReference())
1349           .addStatement("val blockTaco = %L", blockTaco.constructorReference())
1350           .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty)
1351           .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty)
1352           .build(),
1353       )
1354       .build()
1355     assertThat(file.toString()).isEqualTo(
1356       """
1357       |package com.example
1358       |
1359       |import kotlin.Unit
1360       |import com.squareup.cash.util.isNullOrEmpty as cashIsNullOrEmpty
1361       |import com.squareup.tacos.Taco as SquareTaco
1362       |import kotlin.text.isNullOrEmpty as kotlinIsNullOrEmpty
1363       |import xyz.block.tacos.Taco as BlockTaco
1364       |
1365       |public fun main(): Unit {
1366       |  val squareTaco = ::SquareTaco
1367       |  val blockTaco = ::BlockTaco
1368       |  val isSquareTacoNull = "Taco".kotlinIsNullOrEmpty()
1369       |  val isBlockTacoNull = "Taco".cashIsNullOrEmpty()
1370       |}
1371       |
1372       """.trimMargin(),
1373     )
1374   }
1375 }
1376