xref: /aosp_15_r20/external/kotlinpoet/kotlinpoet/src/commonTest/kotlin/com/squareup/kotlinpoet/CodeBlockTest.kt (revision 3c321d951dd070fb96f8ba59e952ffc3131379a0)
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 java.util.Locale
21 import kotlin.test.Test
22 
23 class CodeBlockTest {
equalsAndHashCodenull24   @Test fun equalsAndHashCode() {
25     var a = CodeBlock.builder().build()
26     var b = CodeBlock.builder().build()
27     assertThat(a == b).isTrue()
28     assertThat(a.hashCode()).isEqualTo(b.hashCode())
29     a = CodeBlock.builder().add("%L", "taco").build()
30     b = CodeBlock.builder().add("%L", "taco").build()
31     assertThat(a == b).isTrue()
32     assertThat(a.hashCode()).isEqualTo(b.hashCode())
33   }
34 
ofnull35   @Test fun of() {
36     val a = CodeBlock.of("%L taco", "delicious")
37     assertThat(a.toString()).isEqualTo("delicious taco")
38   }
39 
doublePrecisionnull40   @Test fun doublePrecision() {
41     val doubles = listOf(
42       12345678900000.0 to "12_345_678_900_000.0",
43       12345678900000.07 to "12_345_678_900_000.07",
44       123456.0 to "123_456.0",
45       1234.5678 to "1_234.5678",
46       12.345678 to "12.345678",
47       0.12345678 to "0.12345678",
48       0.0001 to "0.0001",
49       0.00001 to "0.00001",
50       0.000001 to "0.000001",
51       0.0000001 to "0.0000001",
52     )
53     for ((d, expected) in doubles) {
54       val a = CodeBlock.of("number %L", d)
55       assertThat(a.toString()).isEqualTo("number $expected")
56     }
57   }
58 
floatPrecisionnull59   @Test fun floatPrecision() {
60     val floats = listOf(
61       12345678.0f to "12_345_678.0",
62       123456.0f to "123_456.0",
63       1234.567f to "1_234.567",
64       12.34567f to "12.34567",
65       0.1234567f to "0.1234567",
66       0.0001f to "0.0001",
67       0.00001f to "0.00001",
68       0.000001f to "0.000001",
69       0.0000001f to "0.0000001",
70     )
71     for ((f, expected) in floats) {
72       val a = CodeBlock.of("number %L", f)
73       assertThat(a.toString()).isEqualTo("number $expected")
74     }
75   }
76 
percentEscapeCannotBeIndexednull77   @Test fun percentEscapeCannotBeIndexed() {
78     assertThrows<IllegalArgumentException> {
79       CodeBlock.builder().add("%1%", "taco").build()
80     }.hasMessageThat().isEqualTo("%% may not have an index")
81   }
82 
nameFormatCanBeIndexednull83   @Test fun nameFormatCanBeIndexed() {
84     val block = CodeBlock.builder().add("%1N", "taco").build()
85     assertThat(block.toString()).isEqualTo("taco")
86   }
87 
literalFormatCanBeIndexednull88   @Test fun literalFormatCanBeIndexed() {
89     val block = CodeBlock.builder().add("%1L", "taco").build()
90     assertThat(block.toString()).isEqualTo("taco")
91   }
92 
stringFormatCanBeIndexednull93   @Test fun stringFormatCanBeIndexed() {
94     val block = CodeBlock.builder().add("%1S", "taco").build()
95     assertThat(block.toString()).isEqualTo("\"taco\"")
96   }
97 
typeFormatCanBeIndexednull98   @Test fun typeFormatCanBeIndexed() {
99     val block = CodeBlock.builder().add("%1T", String::class).build()
100     assertThat(block.toString()).isEqualTo("kotlin.String")
101   }
102 
simpleNamedArgumentnull103   @Test fun simpleNamedArgument() {
104     val map = LinkedHashMap<String, Any>()
105     map["text"] = "taco"
106     val block = CodeBlock.builder().addNamed("%text:S", map).build()
107     assertThat(block.toString()).isEqualTo("\"taco\"")
108   }
109 
repeatedNamedArgumentnull110   @Test fun repeatedNamedArgument() {
111     val map = LinkedHashMap<String, Any>()
112     map["text"] = "tacos"
113     val block = CodeBlock.builder()
114       .addNamed("\"I like \" + %text:S + \". Do you like \" + %text:S + \"?\"", map)
115       .build()
116     assertThat(block.toString()).isEqualTo(
117       "\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"",
118     )
119   }
120 
namedAndNoArgFormatnull121   @Test fun namedAndNoArgFormat() {
122     val map = LinkedHashMap<String, Any>()
123     map["text"] = "tacos"
124     val block = CodeBlock.builder()
125       .addNamed("⇥\n%text:L for\n⇤%%3.50", map).build()
126     assertThat(block.toString()).isEqualTo("\n  tacos for\n%3.50")
127   }
128 
missingNamedArgumentnull129   @Test fun missingNamedArgument() {
130     assertThrows<IllegalArgumentException> {
131       val map = LinkedHashMap<String, Any>()
132       CodeBlock.builder().addNamed("%text:S", map).build()
133     }.hasMessageThat().isEqualTo("Missing named argument for %text")
134   }
135 
lowerCaseNamednull136   @Test fun lowerCaseNamed() {
137     assertThrows<IllegalArgumentException> {
138       val map = LinkedHashMap<String, Any>()
139       map["Text"] = "tacos"
140       CodeBlock.builder().addNamed("%Text:S", map).build()
141     }.hasMessageThat().isEqualTo("argument 'Text' must start with a lowercase character")
142   }
143 
multipleNamedArgumentsnull144   @Test fun multipleNamedArguments() {
145     val map = LinkedHashMap<String, Any>()
146     map["pipe"] = System::class
147     map["text"] = "tacos"
148 
149     val block = CodeBlock.builder()
150       .addNamed("%pipe:T.out.println(\"Let's eat some %text:L\");", map)
151       .build()
152 
153     assertThat(block.toString()).isEqualTo(
154       "java.lang.System.out.println(\"Let's eat some tacos\");",
155     )
156   }
157 
namedNewlinenull158   @Test fun namedNewline() {
159     val map = LinkedHashMap<String, Any>()
160     map["clazz"] = java.lang.Integer::class
161     val block = CodeBlock.builder().addNamed("%clazz:T\n", map).build()
162     assertThat(block.toString()).isEqualTo("kotlin.Int\n")
163   }
164 
danglingNamednull165   @Test fun danglingNamed() {
166     val map = LinkedHashMap<String, Any>()
167     map["clazz"] = Int::class
168     assertThrows<IllegalArgumentException> {
169       CodeBlock.builder().addNamed("%clazz:T%", map).build()
170     }.hasMessageThat().isEqualTo("dangling % at end")
171   }
172 
indexTooHighnull173   @Test fun indexTooHigh() {
174     assertThrows<IllegalArgumentException> {
175       CodeBlock.builder().add("%2T", String::class).build()
176     }.hasMessageThat().isEqualTo("index 2 for '%2T' not in range (received 1 arguments)")
177   }
178 
indexIsZeronull179   @Test fun indexIsZero() {
180     assertThrows<IllegalArgumentException> {
181       CodeBlock.builder().add("%0T", String::class).build()
182     }.hasMessageThat().isEqualTo("index 0 for '%0T' not in range (received 1 arguments)")
183   }
184 
indexIsNegativenull185   @Test fun indexIsNegative() {
186     assertThrows<IllegalArgumentException> {
187       CodeBlock.builder().add("%-1T", String::class).build()
188     }.hasMessageThat().isEqualTo("invalid format string: '%-1T'")
189   }
190 
indexWithoutFormatTypenull191   @Test fun indexWithoutFormatType() {
192     assertThrows<IllegalArgumentException> {
193       CodeBlock.builder().add("%1", String::class).build()
194     }.hasMessageThat().isEqualTo("dangling format characters in '%1'")
195   }
196 
indexWithoutFormatTypeNotAtStringEndnull197   @Test fun indexWithoutFormatTypeNotAtStringEnd() {
198     assertThrows<IllegalArgumentException> {
199       CodeBlock.builder().add("%1 taco", String::class).build()
200     }.hasMessageThat().isEqualTo("invalid format string: '%1 taco'")
201   }
202 
indexButNoArgumentsnull203   @Test fun indexButNoArguments() {
204     assertThrows<IllegalArgumentException> {
205       CodeBlock.builder().add("%1T").build()
206     }.hasMessageThat().isEqualTo("index 1 for '%1T' not in range (received 0 arguments)")
207   }
208 
formatIndicatorAlonenull209   @Test fun formatIndicatorAlone() {
210     assertThrows<IllegalArgumentException> {
211       CodeBlock.builder().add("%", String::class).build()
212     }.hasMessageThat().isEqualTo("dangling format characters in '%'")
213   }
214 
formatIndicatorWithoutIndexOrFormatTypenull215   @Test fun formatIndicatorWithoutIndexOrFormatType() {
216     assertThrows<IllegalArgumentException> {
217       CodeBlock.builder().add("% tacoString", String::class).build()
218     }.hasMessageThat().isEqualTo("invalid format string: '% tacoString'")
219   }
220 
sameIndexCanBeUsedWithDifferentFormatsnull221   @Test fun sameIndexCanBeUsedWithDifferentFormats() {
222     val block = CodeBlock.builder()
223       .add("%1T.out.println(%1S)", System::class.asClassName())
224       .build()
225     assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"java.lang.System\")")
226   }
227 
tooManyStatementEntersnull228   @Test fun tooManyStatementEnters() {
229     val codeBlock = CodeBlock.builder()
230       .addStatement("print(«%L»)", "1 + 1")
231       .build()
232     assertThrows<IllegalStateException> {
233       // We can't report this error until rendering type because code blocks might be composed.
234       codeBlock.toString()
235     }.hasMessageThat().isEqualTo(
236       """
237       |Can't open a new statement until the current statement is closed (opening « followed
238       |by another « without a closing »).
239       |Current code block:
240       |- Format parts: [«, print(, «, %L, », ), \n, »]
241       |- Arguments: [1 + 1]
242       |
243       """.trimMargin(),
244     )
245   }
246 
statementExitWithoutStatementEnternull247   @Test fun statementExitWithoutStatementEnter() {
248     val codeBlock = CodeBlock.builder()
249       .addStatement("print(%L»)", "1 + 1")
250       .build()
251     assertThrows<IllegalStateException> {
252       // We can't report this error until rendering type because code blocks might be composed.
253       codeBlock.toString()
254     }.hasMessageThat().isEqualTo(
255       """
256       |Can't close a statement that hasn't been opened (closing » is not preceded by an
257       |opening «).
258       |Current code block:
259       |- Format parts: [«, print(, %L, », ), \n, »]
260       |- Arguments: [1 + 1]
261       |
262       """.trimMargin(),
263     )
264   }
265 
nullableTypenull266   @Test fun nullableType() {
267     val type = String::class.asTypeName().copy(nullable = true)
268     val typeBlock = CodeBlock.of("%T", type)
269     assertThat(typeBlock.toString()).isEqualTo("kotlin.String?")
270 
271     val list = (List::class.asClassName().copy(nullable = true) as ClassName)
272       .parameterizedBy(Int::class.asTypeName().copy(nullable = true))
273       .copy(nullable = true)
274     val listBlock = CodeBlock.of("%T", list)
275     assertThat(listBlock.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?")
276 
277     val map = (Map::class.asClassName().copy(nullable = true) as ClassName)
278       .parameterizedBy(String::class.asTypeName().copy(nullable = true), list)
279       .copy(nullable = true)
280     val mapBlock = CodeBlock.of("%T", map)
281     assertThat(mapBlock.toString())
282       .isEqualTo("kotlin.collections.Map<kotlin.String?, kotlin.collections.List<kotlin.Int?>?>?")
283 
284     val rarr = WildcardTypeName.producerOf(String::class.asTypeName().copy(nullable = true))
285     val rarrBlock = CodeBlock.of("%T", rarr)
286     assertThat(rarrBlock.toString()).isEqualTo("out kotlin.String?")
287   }
288 
withoutPrefixMatchingnull289   @Test fun withoutPrefixMatching() {
290     assertThat(
291       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
292         .withoutPrefix(CodeBlock.of("")),
293     )
294       .isEqualTo(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y"))
295     assertThat(
296       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
297         .withoutPrefix(CodeBlock.of("ab")),
298     )
299       .isEqualTo(CodeBlock.of("cd %S efgh %S ijkl", "x", "y"))
300     assertThat(
301       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
302         .withoutPrefix(CodeBlock.of("abcd ")),
303     )
304       .isEqualTo(CodeBlock.of("%S efgh %S ijkl", "x", "y"))
305     assertThat(
306       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
307         .withoutPrefix(CodeBlock.of("abcd %S", "x")),
308     )
309       .isEqualTo(CodeBlock.of(" efgh %S ijkl", "y"))
310     assertThat(
311       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
312         .withoutPrefix(CodeBlock.of("abcd %S ef", "x")),
313     )
314       .isEqualTo(CodeBlock.of("gh %S ijkl", "y"))
315     assertThat(
316       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
317         .withoutPrefix(CodeBlock.of("abcd %S efgh ", "x")),
318     )
319       .isEqualTo(CodeBlock.of("%S ijkl", "y"))
320     assertThat(
321       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
322         .withoutPrefix(CodeBlock.of("abcd %S efgh %S", "x", "y")),
323     )
324       .isEqualTo(CodeBlock.of(" ijkl"))
325     assertThat(
326       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
327         .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "y")),
328     )
329       .isEqualTo(CodeBlock.of("kl"))
330     assertThat(
331       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
332         .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
333     )
334       .isEqualTo(CodeBlock.of(""))
335   }
336 
withoutPrefixNoArgsnull337   @Test fun withoutPrefixNoArgs() {
338     assertThat(
339       CodeBlock.of("abcd %% efgh %% ijkl")
340         .withoutPrefix(CodeBlock.of("")),
341     )
342       .isEqualTo(CodeBlock.of("abcd %% efgh %% ijkl"))
343     assertThat(
344       CodeBlock.of("abcd %% efgh %% ijkl")
345         .withoutPrefix(CodeBlock.of("ab")),
346     )
347       .isEqualTo(CodeBlock.of("cd %% efgh %% ijkl"))
348     assertThat(
349       CodeBlock.of("abcd %% efgh %% ijkl")
350         .withoutPrefix(CodeBlock.of("abcd ")),
351     )
352       .isEqualTo(CodeBlock.of("%% efgh %% ijkl"))
353     assertThat(
354       CodeBlock.of("abcd %% efgh %% ijkl")
355         .withoutPrefix(CodeBlock.of("abcd %%")),
356     )
357       .isEqualTo(CodeBlock.of(" efgh %% ijkl"))
358     assertThat(
359       CodeBlock.of("abcd %% efgh %% ijkl")
360         .withoutPrefix(CodeBlock.of("abcd %% ef")),
361     )
362       .isEqualTo(CodeBlock.of("gh %% ijkl"))
363     assertThat(
364       CodeBlock.of("abcd %% efgh %% ijkl")
365         .withoutPrefix(CodeBlock.of("abcd %% efgh ")),
366     )
367       .isEqualTo(CodeBlock.of("%% ijkl"))
368     assertThat(
369       CodeBlock.of("abcd %% efgh %% ijkl")
370         .withoutPrefix(CodeBlock.of("abcd %% efgh %%")),
371     )
372       .isEqualTo(CodeBlock.of(" ijkl"))
373     assertThat(
374       CodeBlock.of("abcd %% efgh %% ijkl")
375         .withoutPrefix(CodeBlock.of("abcd %% efgh %% ij")),
376     )
377       .isEqualTo(CodeBlock.of("kl"))
378     assertThat(
379       CodeBlock.of("abcd %% efgh %% ijkl")
380         .withoutPrefix(CodeBlock.of("abcd %% efgh %% ijkl")),
381     )
382       .isEqualTo(CodeBlock.of(""))
383   }
384 
withoutPrefixArgMismatchnull385   @Test fun withoutPrefixArgMismatch() {
386     assertThat(
387       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
388         .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "z")),
389     )
390       .isNull()
391     assertThat(
392       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
393         .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "z", "y")),
394     )
395       .isNull()
396   }
397 
withoutPrefixFormatPartMismatchnull398   @Test fun withoutPrefixFormatPartMismatch() {
399     assertThat(
400       CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
401         .withoutPrefix(CodeBlock.of("abcd %S efgx %S ij", "x", "y")),
402     )
403       .isNull()
404     assertThat(
405       CodeBlock.of("abcd %S efgh %% ijkl", "x")
406         .withoutPrefix(CodeBlock.of("abcd %% efgh %S ij", "x")),
407     )
408       .isNull()
409   }
410 
withoutPrefixTooShortnull411   @Test fun withoutPrefixTooShort() {
412     assertThat(
413       CodeBlock.of("abcd %S efgh %S", "x", "y")
414         .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
415     )
416       .isNull()
417     assertThat(
418       CodeBlock.of("abcd %S efgh", "x")
419         .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
420     )
421       .isNull()
422   }
423 
trimEmptynull424   @Test fun trimEmpty() {
425     assertThat(CodeBlock.of("").trim())
426       .isEqualTo(CodeBlock.of(""))
427   }
428 
trimNoPlaceholdersnull429   @Test fun trimNoPlaceholders() {
430     assertThat(CodeBlock.of("return null").trim())
431       .isEqualTo(CodeBlock.of("return null"))
432   }
433 
trimPlaceholdersWithArgsnull434   @Test fun trimPlaceholdersWithArgs() {
435     assertThat(CodeBlock.of("return %S", "taco").trim())
436       .isEqualTo(CodeBlock.of("return %S", "taco"))
437   }
438 
trimNoArgPlaceholderMiddlenull439   @Test fun trimNoArgPlaceholderMiddle() {
440     assertThat(CodeBlock.of("this.taco = %S", "taco").trim())
441       .isEqualTo(CodeBlock.of("this.taco = %S", "taco"))
442   }
443 
trimNoArgPlaceholderStartnull444   @Test fun trimNoArgPlaceholderStart() {
445     assertThat(CodeBlock.of("⇥return ").trim())
446       .isEqualTo(CodeBlock.of("return "))
447   }
448 
trimNoArgPlaceholderEndnull449   @Test fun trimNoArgPlaceholderEnd() {
450     assertThat(CodeBlock.of("return ⇥").trim())
451       .isEqualTo(CodeBlock.of("return "))
452   }
453 
trimNoArgPlaceholdersStartEndnull454   @Test fun trimNoArgPlaceholdersStartEnd() {
455     assertThat(CodeBlock.of("«return this»").trim())
456       .isEqualTo(CodeBlock.of("return this"))
457   }
458 
trimMultipleNoArgPlaceholdersnull459   @Test fun trimMultipleNoArgPlaceholders() {
460     assertThat(
461       CodeBlock.of("«return if (x > %L) %S else %S»", 1, "a", "b").trim(),
462     )
463       .isEqualTo(CodeBlock.of("return if (x > %L) %S else %S", 1, "a", "b"))
464   }
465 
trimOnlyNoArgPlaceholdersnull466   @Test fun trimOnlyNoArgPlaceholders() {
467     assertThat(CodeBlock.of("«»⇥⇤").trim())
468       .isEqualTo(CodeBlock.of(""))
469   }
470 
replaceSimplenull471   @Test fun replaceSimple() {
472     assertThat(CodeBlock.of("%%⇥%%").replaceAll("%%", ""))
473       .isEqualTo(CodeBlock.of("⇥"))
474   }
475 
replaceNoMatchesnull476   @Test fun replaceNoMatches() {
477     assertThat(CodeBlock.of("%%⇥%%").replaceAll("⇤", ""))
478       .isEqualTo(CodeBlock.of("%%⇥%%"))
479   }
480 
replaceRegexnull481   @Test fun replaceRegex() {
482     assertThat(CodeBlock.of("%%⇥%%⇤").replaceAll("[⇥|⇤]", ""))
483       .isEqualTo(CodeBlock.of("%%%%"))
484   }
485 
joinToCodenull486   @Test fun joinToCode() {
487     val blocks = listOf(CodeBlock.of("%L", "taco1"), CodeBlock.of("%L", "taco2"), CodeBlock.of("%L", "taco3"))
488     assertThat(blocks.joinToCode(prefix = "(", suffix = ")"))
489       .isEqualTo(CodeBlock.of("(%L, %L, %L)", "taco1", "taco2", "taco3"))
490   }
491 
joinToCodeTransformnull492   @Test fun joinToCodeTransform() {
493     val blocks = listOf("taco1", "taco2", "taco3")
494     assertThat(blocks.joinToCode { CodeBlock.of("%S", it) })
495       .isEqualTo(CodeBlock.of("%S, %S, %S", "taco1", "taco2", "taco3"))
496   }
497 
beginControlFlowWithParamsnull498   @Test fun beginControlFlowWithParams() {
499     val controlFlow = CodeBlock.builder()
500       .beginControlFlow("list.forEach { element ->")
501       .addStatement("println(element)")
502       .endControlFlow()
503       .build()
504     assertThat(controlFlow.toString()).isEqualTo(
505       """
506       |list.forEach { element ->
507       |  println(element)
508       |}
509       |
510       """.trimMargin(),
511     )
512   }
513 
beginControlFlowWithParamsAndTemplateStringnull514   @Test fun beginControlFlowWithParamsAndTemplateString() {
515     val controlFlow = CodeBlock.builder()
516       .beginControlFlow("listOf(\"\${1.toString()}\").forEach { element ->")
517       .addStatement("println(element)")
518       .endControlFlow()
519       .build()
520     assertThat(controlFlow.toString()).isEqualTo(
521       """
522       |listOf("${'$'}{1.toString()}").forEach { element ->
523       |  println(element)
524       |}
525       |
526       """.trimMargin(),
527     )
528   }
529 
buildCodeBlocknull530   @Test fun buildCodeBlock() {
531     val codeBlock = buildCodeBlock {
532       beginControlFlow("if (2 == 2)")
533       addStatement("println(%S)", "foo")
534       nextControlFlow("else")
535       addStatement("println(%S)", "bar")
536       endControlFlow()
537     }
538     assertThat(codeBlock.toString()).isEqualTo(
539       """
540       |if (2 == 2) {
541       |  println("foo")
542       |} else {
543       |  println("bar")
544       |}
545       |
546       """.trimMargin(),
547     )
548   }
549 
nonWrappingControlFlownull550   @Test fun nonWrappingControlFlow() {
551     val file = FileSpec.builder("com.squareup.tacos", "Test")
552       .addFunction(
553         FunSpec.builder("test")
554           .beginControlFlow("if (%1S == %1S)", "Very long string that would wrap the line ")
555           .nextControlFlow("else if (%1S == %1S)", "Long string that would wrap the line 2 ")
556           .endControlFlow()
557           .build(),
558       )
559       .build()
560     assertThat(file.toString()).isEqualTo(
561       """
562       |package com.squareup.tacos
563       |
564       |public fun test() {
565       |  if ("Very long string that would wrap the line " ==
566       |      "Very long string that would wrap the line ") {
567       |  } else if ("Long string that would wrap the line 2 " ==
568       |      "Long string that would wrap the line 2 ") {
569       |  }
570       |}
571       |
572       """.trimMargin(),
573     )
574   }
575 
ensureEndsWithNewLineWithNoArgsnull576   @Test fun ensureEndsWithNewLineWithNoArgs() {
577     val codeBlock = CodeBlock.builder()
578       .addStatement("Modeling a kdoc")
579       .add("\n")
580       .addStatement("Statement with no args")
581       .build()
582 
583     assertThat(codeBlock.ensureEndsWithNewLine().toString()).isEqualTo(
584       """
585       |Modeling a kdoc
586       |
587       |Statement with no args
588       |
589       """.trimMargin(),
590     )
591   }
592 
N escapes keywordsnull593   @Test fun `N escapes keywords`() {
594     val funSpec = FunSpec.builder("object").build()
595     assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`object`")
596   }
597 
N escapes spacesnull598   @Test fun `N escapes spaces`() {
599     val funSpec = FunSpec.builder("create taco").build()
600     assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`create taco`")
601   }
602 
clearnull603   @Test fun clear() {
604     val blockBuilder = CodeBlock.builder().addStatement("%S is some existing code", "This")
605 
606     blockBuilder.clear()
607 
608     assertThat(blockBuilder.build().toString()).isEmpty()
609   }
610 
withIndentnull611   @Test fun withIndent() {
612     val codeBlock = CodeBlock.Builder()
613       .apply {
614         addStatement("User(")
615         withIndent {
616           addStatement("age = 42,")
617           addStatement("cities = listOf(")
618           withIndent {
619             addStatement("%S,", "Berlin")
620             addStatement("%S,", "London")
621           }
622           addStatement(")")
623         }
624         addStatement(")")
625       }
626       .build()
627 
628     assertThat(codeBlock.toString()).isEqualTo(
629       """
630       |User(
631       |  age = 42,
632       |  cities = listOf(
633       |    "Berlin",
634       |    "London",
635       |  )
636       |)
637       |
638       """.trimMargin(),
639     )
640   }
641 
642   // https://github.com/square/kotlinpoet/issues/1236
dontEscapeBackslashesInRawStringsnull643   @Test fun dontEscapeBackslashesInRawStrings() {
644     // println("ESCAPE '\\'") -> ESCAPE '\'
645     assertThat(CodeBlock.of("%S", "ESCAPE '\\'").toString()).isEqualTo("\"ESCAPE '\\\\'\"")
646     // println("""ESCAPE '\'""") -> ESCAPE '\'
647     assertThat(CodeBlock.of("%P", """ESCAPE '\'""").toString()).isEqualTo("\"\"\"ESCAPE '\\'\"\"\"")
648   }
649 
650   // https://github.com/square/kotlinpoet/issues/1381
useUnderscoresOnLargeDecimalLiteralsnull651   @Test fun useUnderscoresOnLargeDecimalLiterals() {
652     assertThat(CodeBlock.of("%L", 10000).toString()).isEqualTo("10_000")
653     assertThat(CodeBlock.of("%L", 100000L).toString()).isEqualTo("100_000")
654     assertThat(CodeBlock.of("%L", Int.MIN_VALUE).toString()).isEqualTo("-2_147_483_648")
655     assertThat(CodeBlock.of("%L", Int.MAX_VALUE).toString()).isEqualTo("2_147_483_647")
656     assertThat(CodeBlock.of("%L", Long.MIN_VALUE).toString()).isEqualTo("-9_223_372_036_854_775_808")
657     assertThat(CodeBlock.of("%L", 10000.123).toString()).isEqualTo("10_000.123")
658     assertThat(CodeBlock.of("%L", 3.0).toString()).isEqualTo("3.0")
659     assertThat(CodeBlock.of("%L", 10000.123f).toString()).isEqualTo("10_000.123")
660     assertThat(CodeBlock.of("%L", 10000.123456789012).toString()).isEqualTo("10_000.123456789011")
661     assertThat(CodeBlock.of("%L", 1281.toShort()).toString()).isEqualTo("1_281")
662 
663     assertThat(CodeBlock.of("%S", 10000).toString()).isEqualTo("\"10000\"")
664     assertThat(CodeBlock.of("%S", 100000L).toString()).isEqualTo("\"100000\"")
665     assertThat(CodeBlock.of("%S", Int.MIN_VALUE).toString()).isEqualTo("\"-2147483648\"")
666     assertThat(CodeBlock.of("%S", Int.MAX_VALUE).toString()).isEqualTo("\"2147483647\"")
667     assertThat(CodeBlock.of("%S", Long.MIN_VALUE).toString()).isEqualTo("\"-9223372036854775808\"")
668     assertThat(CodeBlock.of("%S", 10000.123).toString()).isEqualTo("\"10000.123\"")
669     assertThat(CodeBlock.of("%S", 3.0).toString()).isEqualTo("\"3.0\"")
670     assertThat(CodeBlock.of("%S", 10000.123f).toString()).isEqualTo("\"10000.123\"")
671     assertThat(CodeBlock.of("%S", 10000.12345678901).toString()).isEqualTo("\"10000.12345678901\"")
672     assertThat(CodeBlock.of("%S", 1281.toShort()).toString()).isEqualTo("\"1281\"")
673   }
674 
675   // https://github.com/square/kotlinpoet/issues/1657
minusSignInSwedishLocalenull676   @Test fun minusSignInSwedishLocale() {
677     val defaultLocale = Locale.getDefault()
678     Locale.setDefault(Locale.forLanguageTag("sv"))
679 
680     val i = -42
681     val s = CodeBlock.of("val i = %L", i)
682     assertThat(s.toString()).isEqualTo("val i = -42")
683 
684     Locale.setDefault(defaultLocale)
685   }
686 }
687