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