1 /* 2 * Copyright (C) 2015 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 kotlin.test.Test 21 22 class CodeBlockTest { equalsAndHashCodenull23 @Test fun equalsAndHashCode() { 24 var a = CodeBlock.builder().build() 25 var b = CodeBlock.builder().build() 26 assertThat(a == b).isTrue() 27 assertThat(a.hashCode()).isEqualTo(b.hashCode()) 28 a = CodeBlock.builder().add("%L", "taco").build() 29 b = CodeBlock.builder().add("%L", "taco").build() 30 assertThat(a == b).isTrue() 31 assertThat(a.hashCode()).isEqualTo(b.hashCode()) 32 } 33 ofnull34 @Test fun of() { 35 val a = CodeBlock.of("%L taco", "delicious") 36 assertThat(a.toString()).isEqualTo("delicious taco") 37 } 38 percentEscapeCannotBeIndexednull39 @Test fun percentEscapeCannotBeIndexed() { 40 assertThrows<IllegalArgumentException> { 41 CodeBlock.builder().add("%1%", "taco").build() 42 }.hasMessageThat().isEqualTo("%% may not have an index") 43 } 44 nameFormatCanBeIndexednull45 @Test fun nameFormatCanBeIndexed() { 46 val block = CodeBlock.builder().add("%1N", "taco").build() 47 assertThat(block.toString()).isEqualTo("taco") 48 } 49 literalFormatCanBeIndexednull50 @Test fun literalFormatCanBeIndexed() { 51 val block = CodeBlock.builder().add("%1L", "taco").build() 52 assertThat(block.toString()).isEqualTo("taco") 53 } 54 stringFormatCanBeIndexednull55 @Test fun stringFormatCanBeIndexed() { 56 val block = CodeBlock.builder().add("%1S", "taco").build() 57 assertThat(block.toString()).isEqualTo("\"taco\"") 58 } 59 typeFormatCanBeIndexednull60 @Test fun typeFormatCanBeIndexed() { 61 val block = CodeBlock.builder().add("%1T", String::class).build() 62 assertThat(block.toString()).isEqualTo("kotlin.String") 63 } 64 simpleNamedArgumentnull65 @Test fun simpleNamedArgument() { 66 val map = LinkedHashMap<String, Any>() 67 map["text"] = "taco" 68 val block = CodeBlock.builder().addNamed("%text:S", map).build() 69 assertThat(block.toString()).isEqualTo("\"taco\"") 70 } 71 repeatedNamedArgumentnull72 @Test fun repeatedNamedArgument() { 73 val map = LinkedHashMap<String, Any>() 74 map["text"] = "tacos" 75 val block = CodeBlock.builder() 76 .addNamed("\"I like \" + %text:S + \". Do you like \" + %text:S + \"?\"", map) 77 .build() 78 assertThat(block.toString()).isEqualTo( 79 "\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"", 80 ) 81 } 82 namedAndNoArgFormatnull83 @Test fun namedAndNoArgFormat() { 84 val map = LinkedHashMap<String, Any>() 85 map["text"] = "tacos" 86 val block = CodeBlock.builder() 87 .addNamed("⇥\n%text:L for\n⇤%%3.50", map).build() 88 assertThat(block.toString()).isEqualTo("\n tacos for\n%3.50") 89 } 90 missingNamedArgumentnull91 @Test fun missingNamedArgument() { 92 assertThrows<IllegalArgumentException> { 93 val map = LinkedHashMap<String, Any>() 94 CodeBlock.builder().addNamed("%text:S", map).build() 95 }.hasMessageThat().isEqualTo("Missing named argument for %text") 96 } 97 lowerCaseNamednull98 @Test fun lowerCaseNamed() { 99 assertThrows<IllegalArgumentException> { 100 val map = LinkedHashMap<String, Any>() 101 map["Text"] = "tacos" 102 CodeBlock.builder().addNamed("%Text:S", map).build() 103 }.hasMessageThat().isEqualTo("argument 'Text' must start with a lowercase character") 104 } 105 multipleNamedArgumentsnull106 @Test fun multipleNamedArguments() { 107 val map = LinkedHashMap<String, Any>() 108 map["pipe"] = System::class 109 map["text"] = "tacos" 110 111 val block = CodeBlock.builder() 112 .addNamed("%pipe:T.out.println(\"Let's eat some %text:L\");", map) 113 .build() 114 115 assertThat(block.toString()).isEqualTo( 116 "java.lang.System.out.println(\"Let's eat some tacos\");", 117 ) 118 } 119 namedNewlinenull120 @Test fun namedNewline() { 121 val map = LinkedHashMap<String, Any>() 122 map["clazz"] = java.lang.Integer::class 123 val block = CodeBlock.builder().addNamed("%clazz:T\n", map).build() 124 assertThat(block.toString()).isEqualTo("kotlin.Int\n") 125 } 126 danglingNamednull127 @Test fun danglingNamed() { 128 val map = LinkedHashMap<String, Any>() 129 map["clazz"] = Int::class 130 assertThrows<IllegalArgumentException> { 131 CodeBlock.builder().addNamed("%clazz:T%", map).build() 132 }.hasMessageThat().isEqualTo("dangling % at end") 133 } 134 indexTooHighnull135 @Test fun indexTooHigh() { 136 assertThrows<IllegalArgumentException> { 137 CodeBlock.builder().add("%2T", String::class).build() 138 }.hasMessageThat().isEqualTo("index 2 for '%2T' not in range (received 1 arguments)") 139 } 140 indexIsZeronull141 @Test fun indexIsZero() { 142 assertThrows<IllegalArgumentException> { 143 CodeBlock.builder().add("%0T", String::class).build() 144 }.hasMessageThat().isEqualTo("index 0 for '%0T' not in range (received 1 arguments)") 145 } 146 indexIsNegativenull147 @Test fun indexIsNegative() { 148 assertThrows<IllegalArgumentException> { 149 CodeBlock.builder().add("%-1T", String::class).build() 150 }.hasMessageThat().isEqualTo("invalid format string: '%-1T'") 151 } 152 indexWithoutFormatTypenull153 @Test fun indexWithoutFormatType() { 154 assertThrows<IllegalArgumentException> { 155 CodeBlock.builder().add("%1", String::class).build() 156 }.hasMessageThat().isEqualTo("dangling format characters in '%1'") 157 } 158 indexWithoutFormatTypeNotAtStringEndnull159 @Test fun indexWithoutFormatTypeNotAtStringEnd() { 160 assertThrows<IllegalArgumentException> { 161 CodeBlock.builder().add("%1 taco", String::class).build() 162 }.hasMessageThat().isEqualTo("invalid format string: '%1 taco'") 163 } 164 indexButNoArgumentsnull165 @Test fun indexButNoArguments() { 166 assertThrows<IllegalArgumentException> { 167 CodeBlock.builder().add("%1T").build() 168 }.hasMessageThat().isEqualTo("index 1 for '%1T' not in range (received 0 arguments)") 169 } 170 formatIndicatorAlonenull171 @Test fun formatIndicatorAlone() { 172 assertThrows<IllegalArgumentException> { 173 CodeBlock.builder().add("%", String::class).build() 174 }.hasMessageThat().isEqualTo("dangling format characters in '%'") 175 } 176 formatIndicatorWithoutIndexOrFormatTypenull177 @Test fun formatIndicatorWithoutIndexOrFormatType() { 178 assertThrows<IllegalArgumentException> { 179 CodeBlock.builder().add("% tacoString", String::class).build() 180 }.hasMessageThat().isEqualTo("invalid format string: '% tacoString'") 181 } 182 sameIndexCanBeUsedWithDifferentFormatsnull183 @Test fun sameIndexCanBeUsedWithDifferentFormats() { 184 val block = CodeBlock.builder() 185 .add("%1T.out.println(%1S)", System::class.asClassName()) 186 .build() 187 assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"java.lang.System\")") 188 } 189 tooManyStatementEntersnull190 @Test fun tooManyStatementEnters() { 191 val codeBlock = CodeBlock.builder() 192 .addStatement("print(«%L»)", "1 + 1") 193 .build() 194 assertThrows<IllegalStateException> { 195 // We can't report this error until rendering type because code blocks might be composed. 196 codeBlock.toString() 197 }.hasMessageThat().isEqualTo( 198 """ 199 |Can't open a new statement until the current statement is closed (opening « followed 200 |by another « without a closing »). 201 |Current code block: 202 |- Format parts: [«, print(, «, %L, », ), \n, »] 203 |- Arguments: [1 + 1] 204 | 205 """.trimMargin(), 206 ) 207 } 208 statementExitWithoutStatementEnternull209 @Test fun statementExitWithoutStatementEnter() { 210 val codeBlock = CodeBlock.builder() 211 .addStatement("print(%L»)", "1 + 1") 212 .build() 213 assertThrows<IllegalStateException> { 214 // We can't report this error until rendering type because code blocks might be composed. 215 codeBlock.toString() 216 }.hasMessageThat().isEqualTo( 217 """ 218 |Can't close a statement that hasn't been opened (closing » is not preceded by an 219 |opening «). 220 |Current code block: 221 |- Format parts: [«, print(, %L, », ), \n, »] 222 |- Arguments: [1 + 1] 223 | 224 """.trimMargin(), 225 ) 226 } 227 nullableTypenull228 @Test fun nullableType() { 229 val type = String::class.asTypeName().copy(nullable = true) 230 val typeBlock = CodeBlock.of("%T", type) 231 assertThat(typeBlock.toString()).isEqualTo("kotlin.String?") 232 233 val list = (List::class.asClassName().copy(nullable = true) as ClassName) 234 .parameterizedBy(Int::class.asTypeName().copy(nullable = true)) 235 .copy(nullable = true) 236 val listBlock = CodeBlock.of("%T", list) 237 assertThat(listBlock.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?") 238 239 val map = (Map::class.asClassName().copy(nullable = true) as ClassName) 240 .parameterizedBy(String::class.asTypeName().copy(nullable = true), list) 241 .copy(nullable = true) 242 val mapBlock = CodeBlock.of("%T", map) 243 assertThat(mapBlock.toString()) 244 .isEqualTo("kotlin.collections.Map<kotlin.String?, kotlin.collections.List<kotlin.Int?>?>?") 245 246 val rarr = WildcardTypeName.producerOf(String::class.asTypeName().copy(nullable = true)) 247 val rarrBlock = CodeBlock.of("%T", rarr) 248 assertThat(rarrBlock.toString()).isEqualTo("out kotlin.String?") 249 } 250 withoutPrefixMatchingnull251 @Test fun withoutPrefixMatching() { 252 assertThat( 253 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 254 .withoutPrefix(CodeBlock.of("")), 255 ) 256 .isEqualTo(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")) 257 assertThat( 258 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 259 .withoutPrefix(CodeBlock.of("ab")), 260 ) 261 .isEqualTo(CodeBlock.of("cd %S efgh %S ijkl", "x", "y")) 262 assertThat( 263 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 264 .withoutPrefix(CodeBlock.of("abcd ")), 265 ) 266 .isEqualTo(CodeBlock.of("%S efgh %S ijkl", "x", "y")) 267 assertThat( 268 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 269 .withoutPrefix(CodeBlock.of("abcd %S", "x")), 270 ) 271 .isEqualTo(CodeBlock.of(" efgh %S ijkl", "y")) 272 assertThat( 273 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 274 .withoutPrefix(CodeBlock.of("abcd %S ef", "x")), 275 ) 276 .isEqualTo(CodeBlock.of("gh %S ijkl", "y")) 277 assertThat( 278 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 279 .withoutPrefix(CodeBlock.of("abcd %S efgh ", "x")), 280 ) 281 .isEqualTo(CodeBlock.of("%S ijkl", "y")) 282 assertThat( 283 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 284 .withoutPrefix(CodeBlock.of("abcd %S efgh %S", "x", "y")), 285 ) 286 .isEqualTo(CodeBlock.of(" ijkl")) 287 assertThat( 288 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 289 .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "y")), 290 ) 291 .isEqualTo(CodeBlock.of("kl")) 292 assertThat( 293 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 294 .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")), 295 ) 296 .isEqualTo(CodeBlock.of("")) 297 } 298 withoutPrefixNoArgsnull299 @Test fun withoutPrefixNoArgs() { 300 assertThat( 301 CodeBlock.of("abcd %% efgh %% ijkl") 302 .withoutPrefix(CodeBlock.of("")), 303 ) 304 .isEqualTo(CodeBlock.of("abcd %% efgh %% ijkl")) 305 assertThat( 306 CodeBlock.of("abcd %% efgh %% ijkl") 307 .withoutPrefix(CodeBlock.of("ab")), 308 ) 309 .isEqualTo(CodeBlock.of("cd %% efgh %% ijkl")) 310 assertThat( 311 CodeBlock.of("abcd %% efgh %% ijkl") 312 .withoutPrefix(CodeBlock.of("abcd ")), 313 ) 314 .isEqualTo(CodeBlock.of("%% efgh %% ijkl")) 315 assertThat( 316 CodeBlock.of("abcd %% efgh %% ijkl") 317 .withoutPrefix(CodeBlock.of("abcd %%")), 318 ) 319 .isEqualTo(CodeBlock.of(" efgh %% ijkl")) 320 assertThat( 321 CodeBlock.of("abcd %% efgh %% ijkl") 322 .withoutPrefix(CodeBlock.of("abcd %% ef")), 323 ) 324 .isEqualTo(CodeBlock.of("gh %% ijkl")) 325 assertThat( 326 CodeBlock.of("abcd %% efgh %% ijkl") 327 .withoutPrefix(CodeBlock.of("abcd %% efgh ")), 328 ) 329 .isEqualTo(CodeBlock.of("%% ijkl")) 330 assertThat( 331 CodeBlock.of("abcd %% efgh %% ijkl") 332 .withoutPrefix(CodeBlock.of("abcd %% efgh %%")), 333 ) 334 .isEqualTo(CodeBlock.of(" ijkl")) 335 assertThat( 336 CodeBlock.of("abcd %% efgh %% ijkl") 337 .withoutPrefix(CodeBlock.of("abcd %% efgh %% ij")), 338 ) 339 .isEqualTo(CodeBlock.of("kl")) 340 assertThat( 341 CodeBlock.of("abcd %% efgh %% ijkl") 342 .withoutPrefix(CodeBlock.of("abcd %% efgh %% ijkl")), 343 ) 344 .isEqualTo(CodeBlock.of("")) 345 } 346 withoutPrefixArgMismatchnull347 @Test fun withoutPrefixArgMismatch() { 348 assertThat( 349 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 350 .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "z")), 351 ) 352 .isNull() 353 assertThat( 354 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 355 .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "z", "y")), 356 ) 357 .isNull() 358 } 359 withoutPrefixFormatPartMismatchnull360 @Test fun withoutPrefixFormatPartMismatch() { 361 assertThat( 362 CodeBlock.of("abcd %S efgh %S ijkl", "x", "y") 363 .withoutPrefix(CodeBlock.of("abcd %S efgx %S ij", "x", "y")), 364 ) 365 .isNull() 366 assertThat( 367 CodeBlock.of("abcd %S efgh %% ijkl", "x") 368 .withoutPrefix(CodeBlock.of("abcd %% efgh %S ij", "x")), 369 ) 370 .isNull() 371 } 372 withoutPrefixTooShortnull373 @Test fun withoutPrefixTooShort() { 374 assertThat( 375 CodeBlock.of("abcd %S efgh %S", "x", "y") 376 .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")), 377 ) 378 .isNull() 379 assertThat( 380 CodeBlock.of("abcd %S efgh", "x") 381 .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")), 382 ) 383 .isNull() 384 } 385 trimEmptynull386 @Test fun trimEmpty() { 387 assertThat(CodeBlock.of("").trim()) 388 .isEqualTo(CodeBlock.of("")) 389 } 390 trimNoPlaceholdersnull391 @Test fun trimNoPlaceholders() { 392 assertThat(CodeBlock.of("return null").trim()) 393 .isEqualTo(CodeBlock.of("return null")) 394 } 395 trimPlaceholdersWithArgsnull396 @Test fun trimPlaceholdersWithArgs() { 397 assertThat(CodeBlock.of("return %S", "taco").trim()) 398 .isEqualTo(CodeBlock.of("return %S", "taco")) 399 } 400 trimNoArgPlaceholderMiddlenull401 @Test fun trimNoArgPlaceholderMiddle() { 402 assertThat(CodeBlock.of("this.taco = %S", "taco").trim()) 403 .isEqualTo(CodeBlock.of("this.taco = %S", "taco")) 404 } 405 trimNoArgPlaceholderStartnull406 @Test fun trimNoArgPlaceholderStart() { 407 assertThat(CodeBlock.of("⇥return ").trim()) 408 .isEqualTo(CodeBlock.of("return ")) 409 } 410 trimNoArgPlaceholderEndnull411 @Test fun trimNoArgPlaceholderEnd() { 412 assertThat(CodeBlock.of("return ⇥").trim()) 413 .isEqualTo(CodeBlock.of("return ")) 414 } 415 trimNoArgPlaceholdersStartEndnull416 @Test fun trimNoArgPlaceholdersStartEnd() { 417 assertThat(CodeBlock.of("«return this»").trim()) 418 .isEqualTo(CodeBlock.of("return this")) 419 } 420 trimMultipleNoArgPlaceholdersnull421 @Test fun trimMultipleNoArgPlaceholders() { 422 assertThat( 423 CodeBlock.of("«return if (x > %L) %S else %S»", 1, "a", "b").trim(), 424 ) 425 .isEqualTo(CodeBlock.of("return if (x > %L) %S else %S", 1, "a", "b")) 426 } 427 trimOnlyNoArgPlaceholdersnull428 @Test fun trimOnlyNoArgPlaceholders() { 429 assertThat(CodeBlock.of("«»⇥⇤").trim()) 430 .isEqualTo(CodeBlock.of("")) 431 } 432 replaceSimplenull433 @Test fun replaceSimple() { 434 assertThat(CodeBlock.of("%%⇥%%").replaceAll("%%", "")) 435 .isEqualTo(CodeBlock.of("⇥")) 436 } 437 replaceNoMatchesnull438 @Test fun replaceNoMatches() { 439 assertThat(CodeBlock.of("%%⇥%%").replaceAll("⇤", "")) 440 .isEqualTo(CodeBlock.of("%%⇥%%")) 441 } 442 replaceRegexnull443 @Test fun replaceRegex() { 444 assertThat(CodeBlock.of("%%⇥%%⇤").replaceAll("[⇥|⇤]", "")) 445 .isEqualTo(CodeBlock.of("%%%%")) 446 } 447 joinToCodenull448 @Test fun joinToCode() { 449 val blocks = listOf(CodeBlock.of("%L", "taco1"), CodeBlock.of("%L", "taco2"), CodeBlock.of("%L", "taco3")) 450 assertThat(blocks.joinToCode(prefix = "(", suffix = ")")) 451 .isEqualTo(CodeBlock.of("(%L, %L, %L)", "taco1", "taco2", "taco3")) 452 } 453 beginControlFlowWithParamsnull454 @Test fun beginControlFlowWithParams() { 455 val controlFlow = CodeBlock.builder() 456 .beginControlFlow("list.forEach { element ->") 457 .addStatement("println(element)") 458 .endControlFlow() 459 .build() 460 assertThat(controlFlow.toString()).isEqualTo( 461 """ 462 |list.forEach { element -> 463 | println(element) 464 |} 465 | 466 """.trimMargin(), 467 ) 468 } 469 beginControlFlowWithParamsAndTemplateStringnull470 @Test fun beginControlFlowWithParamsAndTemplateString() { 471 val controlFlow = CodeBlock.builder() 472 .beginControlFlow("listOf(\"\${1.toString()}\").forEach { element ->") 473 .addStatement("println(element)") 474 .endControlFlow() 475 .build() 476 assertThat(controlFlow.toString()).isEqualTo( 477 """ 478 |listOf("${'$'}{1.toString()}").forEach { element -> 479 | println(element) 480 |} 481 | 482 """.trimMargin(), 483 ) 484 } 485 buildCodeBlocknull486 @Test fun buildCodeBlock() { 487 val codeBlock = buildCodeBlock { 488 beginControlFlow("if (2 == 2)") 489 addStatement("println(%S)", "foo") 490 nextControlFlow("else") 491 addStatement("println(%S)", "bar") 492 endControlFlow() 493 } 494 assertThat(codeBlock.toString()).isEqualTo( 495 """ 496 |if (2 == 2) { 497 | println("foo") 498 |} else { 499 | println("bar") 500 |} 501 | 502 """.trimMargin(), 503 ) 504 } 505 nonWrappingControlFlownull506 @Test fun nonWrappingControlFlow() { 507 val file = FileSpec.builder("com.squareup.tacos", "Test") 508 .addFunction( 509 FunSpec.builder("test") 510 .beginControlFlow("if (%1S == %1S)", "Very long string that would wrap the line ") 511 .nextControlFlow("else if (%1S == %1S)", "Long string that would wrap the line 2 ") 512 .endControlFlow() 513 .build(), 514 ) 515 .build() 516 assertThat(file.toString()).isEqualTo( 517 """ 518 |package com.squareup.tacos 519 | 520 |import kotlin.Unit 521 | 522 |public fun test(): Unit { 523 | if ("Very long string that would wrap the line " == 524 | "Very long string that would wrap the line ") { 525 | } else if ("Long string that would wrap the line 2 " == 526 | "Long string that would wrap the line 2 ") { 527 | } 528 |} 529 | 530 """.trimMargin(), 531 ) 532 } 533 ensureEndsWithNewLineWithNoArgsnull534 @Test fun ensureEndsWithNewLineWithNoArgs() { 535 val codeBlock = CodeBlock.builder() 536 .addStatement("Modeling a kdoc") 537 .add("\n") 538 .addStatement("Statement with no args") 539 .build() 540 541 assertThat(codeBlock.ensureEndsWithNewLine().toString()).isEqualTo( 542 """ 543 |Modeling a kdoc 544 | 545 |Statement with no args 546 | 547 """.trimMargin(), 548 ) 549 } 550 %N escapes keywordsnull551 @Test fun `%N escapes keywords`() { 552 val funSpec = FunSpec.builder("object").build() 553 assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`object`") 554 } 555 %N escapes spacesnull556 @Test fun `%N escapes spaces`() { 557 val funSpec = FunSpec.builder("create taco").build() 558 assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`create taco`") 559 } 560 clearnull561 @Test fun clear() { 562 val blockBuilder = CodeBlock.builder().addStatement("%S is some existing code", "This") 563 564 blockBuilder.clear() 565 566 assertThat(blockBuilder.build().toString()).isEmpty() 567 } 568 withIndentnull569 @Test fun withIndent() { 570 val codeBlock = CodeBlock.Builder() 571 .apply { 572 addStatement("User(") 573 withIndent { 574 addStatement("age = 42,") 575 addStatement("cities = listOf(") 576 withIndent { 577 addStatement("%S,", "Berlin") 578 addStatement("%S,", "London") 579 } 580 addStatement(")") 581 } 582 addStatement(")") 583 } 584 .build() 585 586 assertThat(codeBlock.toString()).isEqualTo( 587 """ 588 |User( 589 | age = 42, 590 | cities = listOf( 591 | "Berlin", 592 | "London", 593 | ) 594 |) 595 | 596 """.trimMargin(), 597 ) 598 } 599 600 // https://github.com/square/kotlinpoet/issues/1236 dontEscapeBackslashesInRawStringsnull601 @Test fun dontEscapeBackslashesInRawStrings() { 602 // println("ESCAPE '\\'") -> ESCAPE '\' 603 assertThat(CodeBlock.of("%S", "ESCAPE '\\'").toString()).isEqualTo("\"ESCAPE '\\\\'\"") 604 // println("""ESCAPE '\'""") -> ESCAPE '\' 605 assertThat(CodeBlock.of("%P", """ESCAPE '\'""").toString()).isEqualTo("\"\"\"ESCAPE '\\'\"\"\"") 606 } 607 608 // https://github.com/square/kotlinpoet/issues/1381 useUnderscoresOnLargeDecimalLiteralsnull609 @Test fun useUnderscoresOnLargeDecimalLiterals() { 610 assertThat(CodeBlock.of("%L", 10000).toString()).isEqualTo("10_000") 611 assertThat(CodeBlock.of("%L", 100000L).toString()).isEqualTo("100_000") 612 assertThat(CodeBlock.of("%L", Int.MIN_VALUE).toString()).isEqualTo("-2_147_483_648") 613 assertThat(CodeBlock.of("%L", Int.MAX_VALUE).toString()).isEqualTo("2_147_483_647") 614 assertThat(CodeBlock.of("%L", Long.MIN_VALUE).toString()).isEqualTo("-9_223_372_036_854_775_808") 615 assertThat(CodeBlock.of("%L", 10000.123).toString()).isEqualTo("10_000.123") 616 assertThat(CodeBlock.of("%L", 3.0).toString()).isEqualTo("3.0") 617 assertThat(CodeBlock.of("%L", 10000.123f).toString()).isEqualTo("10_000.123") 618 assertThat(CodeBlock.of("%L", 10000.123456789012).toString()).isEqualTo("10_000.123456789011") 619 assertThat(CodeBlock.of("%L", 1281.toShort()).toString()).isEqualTo("1_281") 620 621 assertThat(CodeBlock.of("%S", 10000).toString()).isEqualTo("\"10000\"") 622 assertThat(CodeBlock.of("%S", 100000L).toString()).isEqualTo("\"100000\"") 623 assertThat(CodeBlock.of("%S", Int.MIN_VALUE).toString()).isEqualTo("\"-2147483648\"") 624 assertThat(CodeBlock.of("%S", Int.MAX_VALUE).toString()).isEqualTo("\"2147483647\"") 625 assertThat(CodeBlock.of("%S", Long.MIN_VALUE).toString()).isEqualTo("\"-9223372036854775808\"") 626 assertThat(CodeBlock.of("%S", 10000.123).toString()).isEqualTo("\"10000.123\"") 627 assertThat(CodeBlock.of("%S", 3.0).toString()).isEqualTo("\"3.0\"") 628 assertThat(CodeBlock.of("%S", 10000.123f).toString()).isEqualTo("\"10000.123\"") 629 assertThat(CodeBlock.of("%S", 10000.12345678901).toString()).isEqualTo("\"10000.12345678901\"") 630 assertThat(CodeBlock.of("%S", 1281.toShort()).toString()).isEqualTo("\"1281\"") 631 } 632 } 633