| KotlinPoet |
| ========== |
| |
| `KotlinPoet` is a Kotlin and Java API for generating `.kt` source files. |
| |
| Source file generation can be useful when doing things such as annotation processing or interacting |
| with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate |
| the need to write boilerplate while also keeping a single source of truth for the metadata. |
| |
| ### Example |
| |
| Here's a `HelloWorld` file: |
| |
| ```kotlin |
| class Greeter(val name: String) { |
| fun greet() { |
| println("""Hello, $name""") |
| } |
| } |
| |
| fun main(vararg args: String) { |
| Greeter(args[0]).greet() |
| } |
| ``` |
| |
| And this is the code to generate it with KotlinPoet: |
| |
| ```kotlin |
| val greeterClass = ClassName("", "Greeter") |
| val file = FileSpec.builder("", "HelloWorld") |
| .addType( |
| TypeSpec.classBuilder("Greeter") |
| .primaryConstructor( |
| FunSpec.constructorBuilder() |
| .addParameter("name", String::class) |
| .build() |
| ) |
| .addProperty( |
| PropertySpec.builder("name", String::class) |
| .initializer("name") |
| .build() |
| ) |
| .addFunction( |
| FunSpec.builder("greet") |
| .addStatement("println(%P)", "Hello, \$name") |
| .build() |
| ) |
| .build() |
| ) |
| .addFunction( |
| FunSpec.builder("main") |
| .addParameter("args", String::class, VARARG) |
| .addStatement("%T(args[0]).greet()", greeterClass) |
| .build() |
| ) |
| .build() |
| |
| file.writeTo(System.out) |
| ``` |
| |
| The [KDoc][kdoc] catalogs the complete KotlinPoet API, which is inspired by [JavaPoet][javapoet]. |
| |
| **Note:** In order to maximize portability, KotlinPoet generates code with explicit visibility |
| modifiers. This ensures compatibility with both standard Kotlin projects as well as projects |
| using [explicit API mode](https://kotlinlang.org/docs/whatsnew14.html#explicit-api-mode-for-library-authors). |
| Examples in this file omit those modifiers for brevity. |
| |
| ### Code & Control Flow |
| |
| Most of KotlinPoet's API uses immutable Kotlin objects. There's also builders, method chaining |
| and varargs to make the API friendly. KotlinPoet offers models for Kotlin files (`FileSpec`), |
| classes, interfaces & objects (`TypeSpec`), type aliases (`TypeAliasSpec`), |
| properties (`PropertySpec`), functions & constructors (`FunSpec`), parameters (`ParameterSpec`) and |
| annotations (`AnnotationSpec`). |
| |
| But the _body_ of methods and constructors is not modeled. There's no expression class, no |
| statement class or syntax tree nodes. Instead, KotlinPoet uses strings for code blocks, and you can |
| take advantage of Kotlin's multiline strings to make this look nice: |
| |
| ```kotlin |
| val main = FunSpec.builder("main") |
| .addCode(""" |
| |var total = 0 |
| |for (i in 0..<10) { |
| | total += i |
| |} |
| |""".trimMargin()) |
| .build() |
| ``` |
| |
| Which generates this: |
| |
| ```kotlin |
| fun main() { |
| var total = 0 |
| for (i in 0..<10) { |
| total += i |
| } |
| } |
| ``` |
| |
| There are additional APIs to assist with newlines, braces and indentation: |
| |
| ```kotlin |
| val main = FunSpec.builder("main") |
| .addStatement("var total = 0") |
| .beginControlFlow("for (i in 0..<10)") |
| .addStatement("total += i") |
| .endControlFlow() |
| .build() |
| ``` |
| |
| This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10, |
| we want to make the operation and range configurable. Here's a method that generates a method: |
| |
| ```kotlin |
| private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec { |
| return FunSpec.builder(name) |
| .returns(Int::class) |
| .addStatement("var result = 1") |
| .beginControlFlow("for (i in $from..<$to)") |
| .addStatement("result = result $op i") |
| .endControlFlow() |
| .addStatement("return result") |
| .build() |
| } |
| ``` |
| |
| And here's what we get when we call `computeRange("multiply10to20", 10, 20, "*")`: |
| |
| ```kotlin |
| fun multiply10to20(): kotlin.Int { |
| var result = 1 |
| for (i in 10..<20) { |
| result = result * i |
| } |
| return result |
| } |
| ``` |
| |
| Methods generating methods! And since KotlinPoet generates source instead of bytecode, you can |
| read through it to make sure it's right. |
| |
| ### %S for Strings |
| |
| When emitting code that includes string literals, we can use **`%S`** to emit a **string**, complete |
| with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which |
| returns its own name: |
| |
| ```kotlin |
| fun main(args: Array<String>) { |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addFunction(whatsMyNameYo("slimShady")) |
| .addFunction(whatsMyNameYo("eminem")) |
| .addFunction(whatsMyNameYo("marshallMathers")) |
| .build() |
| |
| val kotlinFile = FileSpec.builder("com.example.helloworld", "HelloWorld") |
| .addType(helloWorld) |
| .build() |
| |
| kotlinFile.writeTo(System.out) |
| } |
| |
| private fun whatsMyNameYo(name: String): FunSpec { |
| return FunSpec.builder(name) |
| .returns(String::class) |
| .addStatement("return %S", name) |
| .build() |
| } |
| ``` |
| |
| In this case, using `%S` gives us quotation marks: |
| |
| ```kotlin |
| class HelloWorld { |
| fun slimShady(): String = "slimShady" |
| |
| fun eminem(): String = "eminem" |
| |
| fun marshallMathers(): String = "marshallMathers" |
| } |
| ``` |
| |
| ### %P for String Templates |
| |
| `%S` also handles the escaping of dollar signs (`$`), to avoid inadvertent creation of string |
| templates, which may fail to compile in generated code: |
| |
| ```kotlin |
| val stringWithADollar = "Your total is " + "$" + "50" |
| val funSpec = FunSpec.builder("printTotal") |
| .returns(String::class) |
| .addStatement("return %S", stringWithADollar) |
| .build() |
| ``` |
| |
| produces: |
| |
| ```kotlin |
| fun printTotal(): String = "Your total is ${'$'}50" |
| ``` |
| |
| If you need to generate string templates, use `%P`, which doesn't escape dollars: |
| |
| ```kotlin |
| val amount = 50 |
| val stringWithADollar = "Your total is " + "$" + "amount" |
| val funSpec = FunSpec.builder("printTotal") |
| .returns(String::class) |
| .addStatement("return %P", stringWithADollar) |
| .build() |
| ``` |
| |
| produces: |
| |
| ```kotlin |
| fun printTotal(): String = "Your total is $amount" |
| ``` |
| |
| You can also use `CodeBlock`s as arguments to `%P`, which is handy when you need to reference |
| importable types or members inside the string template: |
| |
| ```kotlin |
| val file = FileSpec.builder("com.example", "Digits") |
| .addFunction( |
| FunSpec.builder("print") |
| .addParameter("digits", IntArray::class) |
| .addStatement("println(%P)", buildCodeBlock { |
| val contentToString = MemberName("kotlin.collections", "contentToString") |
| add("These are the digits: \${digits.%M()}", contentToString) |
| }) |
| .build() |
| ) |
| .build() |
| println(file) |
| ``` |
| |
| The snippet above will produce the following output, handling the imports properly: |
| |
| ```kotlin |
| package com.example |
| |
| import kotlin.IntArray |
| import kotlin.collections.contentToString |
| |
| fun print(digits: IntArray) { |
| println("""These are the digits: ${digits.contentToString()}""") |
| } |
| ``` |
| |
| ### %T for Types |
| |
| KotlinPoet has rich built-in support for types, including automatic generation of `import` |
| statements. Just use **`%T`** to reference **types**: |
| |
| ```kotlin |
| val today = FunSpec.builder("today") |
| .returns(Date::class) |
| .addStatement("return %T()", Date::class) |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addFunction(today) |
| .build() |
| |
| val kotlinFile = FileSpec.builder("com.example.helloworld", "HelloWorld") |
| .addType(helloWorld) |
| .build() |
| |
| kotlinFile.writeTo(System.out) |
| ``` |
| |
| That generates the following `.kt` file, complete with the necessary `import`: |
| |
| ```kotlin |
| package com.example.helloworld |
| |
| import java.util.Date |
| |
| class HelloWorld { |
| fun today(): Date = Date() |
| } |
| ``` |
| |
| We passed `Date::class` to reference a class that just-so-happens to be available when we're |
| generating code. This doesn't need to be the case. Here's a similar example, but this one |
| references a class that doesn't exist (yet): |
| |
| ```kotlin |
| val hoverboard = ClassName("com.mattel", "Hoverboard") |
| |
| val tomorrow = FunSpec.builder("tomorrow") |
| .returns(hoverboard) |
| .addStatement("return %T()", hoverboard) |
| .build() |
| ``` |
| |
| And that not-yet-existent class is imported as well: |
| |
| ```kotlin |
| package com.example.helloworld |
| |
| import com.mattel.Hoverboard |
| |
| class HelloWorld { |
| fun tomorrow(): Hoverboard = Hoverboard() |
| } |
| ``` |
| |
| The `ClassName` type is very important, and you'll need it frequently when you're using KotlinPoet. |
| It can identify any _declared_ class. Declared types are just the beginning of Kotlin's rich type |
| system: we also have arrays, parameterized types, wildcard types, lambda types and type variables. |
| KotlinPoet has classes for building each of these: |
| |
| ```kotlin |
| import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy |
| import com.squareup.kotlinpoet.STAR |
| |
| val hoverboard = ClassName("com.mattel", "Hoverboard") |
| val list = ClassName("kotlin.collections", "List") |
| val arrayList = ClassName("kotlin.collections", "ArrayList") |
| val listOfHoverboards = list.parameterizedBy(hoverboard) |
| val arrayListOfHoverboards = arrayList.parameterizedBy(hoverboard) |
| |
| val thing = ClassName("com.misc", "Thing") |
| val array = ClassName("kotlin", "Array") |
| val producerArrayOfThings = array.parameterizedBy(WildcardTypeName.producerOf(thing)) |
| |
| val beyond = FunSpec.builder("beyond") |
| .returns(listOfHoverboards) |
| .addStatement("val result = %T()", arrayListOfHoverboards) |
| .addStatement("result += %T()", hoverboard) |
| .addStatement("result += %T()", hoverboard) |
| .addStatement("result += %T()", hoverboard) |
| .addStatement("return result") |
| .build() |
| |
| val printThings = FunSpec.builder("printThings") |
| .addParameter("things", producerArrayOfThings) |
| .addStatement("println(things)") |
| .build() |
| |
| val printKClass = FunSpec.builder("printKClass") |
| .addParameter("kClass", KClass::class.asClassName().parameterizedBy(STAR)) |
| .addStatement("println(kClass)") |
| .build() |
| ``` |
| |
| The `STAR` is represented as `*` in KotlinPoet. You can find more in the [KDoc][kdoc]. |
| |
| KotlinPoet will decompose each type and import its components where possible. |
| |
| ```kotlin |
| package com.example.helloworld |
| |
| import com.mattel.Hoverboard |
| import com.misc.Thing |
| import kotlin.Array |
| import kotlin.collections.ArrayList |
| import kotlin.collections.List |
| import kotlin.reflect.KClass |
| |
| class HelloWorld { |
| fun beyond(): List<Hoverboard> { |
| val result = ArrayList<Hoverboard>() |
| result += Hoverboard() |
| result += Hoverboard() |
| result += Hoverboard() |
| return result |
| } |
| |
| fun printThings(things: Array<out Thing>) { |
| println(things) |
| } |
| |
| fun printKClass(kClass: KClass<*>) { |
| println(kClass) |
| } |
| } |
| ``` |
| |
| #### Nullable Types |
| |
| KotlinPoet supports nullable types. To convert a `TypeName` into its nullable counterpart, use the |
| `copy()` method with `nullable` parameter set to `true`: |
| |
| ```kotlin |
| val java = PropertySpec.builder("java", String::class.asTypeName().copy(nullable = true)) |
| .mutable() |
| .addModifiers(KModifier.PRIVATE) |
| .initializer("null") |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addProperty(java) |
| .addProperty("kotlin", String::class, KModifier.PRIVATE) |
| .build() |
| ``` |
| |
| generates: |
| |
| ```kotlin |
| class HelloWorld { |
| private var java: String? = null |
| |
| private val kotlin: String |
| } |
| ``` |
| |
| ### %M for Members |
| |
| Similar to types, KotlinPoet has a special placeholder for **members** (functions and properties), |
| which comes handy when your code needs to access top-level members and members declared inside |
| objects. Use **`%M`** to reference members, pass an instance of `MemberName` as the argument for the |
| placeholder, and KotlinPoet will handle imports automatically: |
| |
| ```kotlin |
| val createTaco = MemberName("com.squareup.tacos", "createTaco") |
| val isVegan = MemberName("com.squareup.tacos", "isVegan") |
| val file = FileSpec.builder("com.squareup.example", "TacoTest") |
| .addFunction( |
| FunSpec.builder("main") |
| .addStatement("val taco = %M()", createTaco) |
| .addStatement("println(taco.%M)", isVegan) |
| .build() |
| ) |
| .build() |
| println(file) |
| ``` |
| |
| The code above generates the following file: |
| |
| ```kotlin |
| package com.squareup.example |
| |
| import com.squareup.tacos.createTaco |
| import com.squareup.tacos.isVegan |
| |
| fun main() { |
| val taco = createTaco() |
| println(taco.isVegan) |
| } |
| ``` |
| |
| As you can see, it's also possible to use `%M` to reference extension functions and properties. You |
| just need to make sure the member can be imported without simple name collisions, otherwise |
| importing will fail and the code generator output will not pass compilation. There's a way to work |
| around such cases though - use `FileSpec.addAliasedImport()` to create an alias for a clashing |
| `MemberName`: |
| |
| ```kotlin |
| val createTaco = MemberName("com.squareup.tacos", "createTaco") |
| val createCake = MemberName("com.squareup.cakes", "createCake") |
| val isTacoVegan = MemberName("com.squareup.tacos", "isVegan") |
| val isCakeVegan = MemberName("com.squareup.cakes", "isVegan") |
| val file = FileSpec.builder("com.squareup.example", "Test") |
| .addAliasedImport(isTacoVegan, "isTacoVegan") |
| .addAliasedImport(isCakeVegan, "isCakeVegan") |
| .addFunction( |
| FunSpec.builder("main") |
| .addStatement("val taco = %M()", createTaco) |
| .addStatement("val cake = %M()", createCake) |
| .addStatement("println(taco.%M)", isTacoVegan) |
| .addStatement("println(cake.%M)", isCakeVegan) |
| .build() |
| ) |
| .build() |
| println(file) |
| ``` |
| |
| KotlinPoet will produce an aliased import for `com.squareup.tacos2.isVegan`: |
| |
| ```kotlin |
| package com.squareup.example |
| |
| import com.squareup.cakes.createCake |
| import com.squareup.tacos.createTaco |
| import com.squareup.cakes.isVegan as isCakeVegan |
| import com.squareup.tacos.isVegan as isTacoVegan |
| |
| fun main() { |
| val taco = createTaco() |
| val cake = createCake() |
| println(taco.isTacoVegan) |
| println(cake.isCakeVegan) |
| } |
| ``` |
| |
| #### MemberName and operators |
| |
| MemberName also supports operators, you can use `MemberName(String, KOperator)` |
| or `MemberName(ClassName, KOperator)` to import and reference operators. |
| |
| ```kotlin |
| val taco = ClassName("com.squareup.tacos", "Taco") |
| val meat = ClassName("com.squareup.tacos.ingredient", "Meat") |
| val iterator = MemberName("com.squareup.tacos.internal", KOperator.ITERATOR) |
| val minusAssign = MemberName("com.squareup.tacos.internal", KOperator.MINUS_ASSIGN) |
| val file = FileSpec.builder("com.example", "Test") |
| .addFunction( |
| FunSpec.builder("makeTacoHealthy") |
| .addParameter("taco", taco) |
| .beginControlFlow("for (ingredient %M taco)", iterator) |
| .addStatement("if (ingredient is %T) taco %M ingredient", meat, minusAssign) |
| .endControlFlow() |
| .addStatement("return taco") |
| .build() |
| ) |
| .build() |
| println(file) |
| ``` |
| |
| KotlinPoet will import the extension operator functions and emit the operator. |
| |
| ```kotlin |
| package com.example |
| |
| import com.squareup.tacos.Taco |
| import com.squareup.tacos.ingredient.Meat |
| import com.squareup.tacos.internal.iterator |
| import com.squareup.tacos.internal.minusAssign |
| |
| fun makeTacoHealthy(taco: Taco) { |
| for (ingredient in taco) { |
| if (ingredient is Meat) taco -= ingredient |
| } |
| return taco |
| } |
| |
| ``` |
| |
| ### %N for Names |
| |
| Generated code is often self-referential. Use **`%N`** to refer to another generated declaration by |
| its name. Here's a method that calls another: |
| |
| ```kotlin |
| fun byteToHex(b: Int): String { |
| val result = CharArray(2) |
| result[0] = hexDigit((b ushr 4) and 0xf) |
| result[1] = hexDigit(b and 0xf) |
| return String(result) |
| } |
| |
| fun hexDigit(i: Int): Char { |
| return (if (i < 10) i + '0'.toInt() else i - 10 + 'a'.toInt()).toChar() |
| } |
| ``` |
| |
| When generating the code above, we pass the `hexDigit()` method as an argument to the `byteToHex()` |
| method using `%N`: |
| |
| ```kotlin |
| val hexDigit = FunSpec.builder("hexDigit") |
| .addParameter("i", Int::class) |
| .returns(Char::class) |
| .addStatement("return (if (i < 10) i + '0'.toInt() else i - 10 + 'a'.toInt()).toChar()") |
| .build() |
| |
| val byteToHex = FunSpec.builder("byteToHex") |
| .addParameter("b", Int::class) |
| .returns(String::class) |
| .addStatement("val result = CharArray(2)") |
| .addStatement("result[0] = %N((b ushr 4) and 0xf)", hexDigit) |
| .addStatement("result[1] = %N(b and 0xf)", hexDigit) |
| .addStatement("return String(result)") |
| .build() |
| ``` |
| |
| Another handy feature that `%N` provides is automatically escaping names that contain illegal |
| identifier characters with double ticks. Suppose your code creates a `MemberName` with a Kotlin |
| keyword as the simple name: |
| |
| ```kotlin |
| val taco = ClassName("com.squareup.tacos", "Taco") |
| val packager = ClassName("com.squareup.tacos", "TacoPackager") |
| val file = FileSpec.builder("com.example", "Test") |
| .addFunction( |
| FunSpec.builder("packageTacos") |
| .addParameter("tacos", LIST.parameterizedBy(taco)) |
| .addParameter("packager", packager) |
| .addStatement("packager.%N(tacos)", packager.member("package")) |
| .build() |
| ) |
| .build() |
| ``` |
| |
| `%N` will escape the name for you, ensuring that the output will pass compilation: |
| |
| ```kotlin |
| package com.example |
| |
| import com.squareup.tacos.Taco |
| import com.squareup.tacos.TacoPackager |
| import kotlin.collections.List |
| |
| fun packageTacos(tacos: List<Taco>, packager: TacoPackager) { |
| packager.`package`(tacos) |
| } |
| ``` |
| |
| ### %L for Literals |
| |
| Although Kotlin's string templates usually work well in cases when you want to include literals into |
| generated code, KotlinPoet offers additional syntax inspired-by but incompatible-with |
| [`String.format()`][formatter]. It accepts **`%L`** to emit a **literal** value in the output. This |
| works just like `Formatter`'s `%s`: |
| |
| ```kotlin |
| private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec { |
| return FunSpec.builder(name) |
| .returns(Int::class) |
| .addStatement("var result = 0") |
| .beginControlFlow("for (i in %L..<%L)", from, to) |
| .addStatement("result = result %L i", op) |
| .endControlFlow() |
| .addStatement("return result") |
| .build() |
| } |
| ``` |
| |
| Literals are emitted directly to the output code with no escaping. Arguments for literals may be |
| strings, primitives, and a few KotlinPoet types described below. |
| |
| ### Code block format strings |
| |
| Code blocks may specify the values for their placeholders in a few ways. Only one style may be used |
| for each operation on a code block. |
| |
| #### Relative Arguments |
| |
| Pass an argument value for each placeholder in the format string to `CodeBlock.add()`. In each |
| example, we generate code to say "I ate 3 tacos" |
| |
| ```kotlin |
| CodeBlock.builder().add("I ate %L %L", 3, "tacos") |
| ``` |
| |
| #### Positional Arguments |
| |
| Place an integer index (1-based) before the placeholder in the format string to specify which |
| argument to use. |
| |
| ```kotlin |
| CodeBlock.builder().add("I ate %2L %1L", "tacos", 3) |
| ``` |
| |
| #### Named Arguments |
| |
| Use the syntax `%argumentName:X` where `X` is the format character and call `CodeBlock.addNamed()` |
| with a map containing all argument keys in the format string. Argument names use characters in |
| `a-z`, `A-Z`, `0-9`, and `_`, and must start with a lowercase character. |
| |
| ```kotlin |
| val map = LinkedHashMap<String, Any>() |
| map += "food" to "tacos" |
| map += "count" to 3 |
| CodeBlock.builder().addNamed("I ate %count:L %food:L", map) |
| ``` |
| |
| ### Functions |
| |
| All of the above functions have a code body. Use `KModifier.ABSTRACT` to get a function without any |
| body. This is only legal if it is enclosed by an abstract class or an interface. |
| |
| ```kotlin |
| val flux = FunSpec.builder("flux") |
| .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED) |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addModifiers(KModifier.ABSTRACT) |
| .addFunction(flux) |
| .build() |
| ``` |
| |
| Which generates this: |
| |
| ```kotlin |
| abstract class HelloWorld { |
| protected abstract fun flux() |
| } |
| ``` |
| |
| The other modifiers work where permitted. |
| |
| Methods also have parameters, varargs, KDoc, annotations, type variables, return type and receiver |
| type for extension functions. All of these are configured with `FunSpec.Builder`. |
| |
| #### Extension functions |
| |
| Extension functions can be generated by specifying a `receiver`. |
| |
| ```kotlin |
| val square = FunSpec.builder("square") |
| .receiver(Int::class) |
| .returns(Int::class) |
| .addStatement("var s = this * this") |
| .addStatement("return s") |
| .build() |
| ``` |
| |
| Which outputs: |
| |
| ```kotlin |
| fun Int.square(): Int { |
| val s = this * this |
| return s |
| } |
| ``` |
| |
| #### Single-expression functions |
| |
| KotlinPoet can recognize single-expression functions and print them out properly. It treats |
| each function with a body that starts with `return` as a single-expression function: |
| |
| ```kotlin |
| val abs = FunSpec.builder("abs") |
| .addParameter("x", Int::class) |
| .returns(Int::class) |
| .addStatement("return if (x < 0) -x else x") |
| .build() |
| ``` |
| |
| Which outputs: |
| |
| ```kotlin |
| fun abs(x: Int): Int = if (x < 0) -x else x |
| ``` |
| |
| #### Default function arguments |
| |
| Consider the example below. |
| Function argument `b` has a default value of 0 to avoid overloading this function. |
| |
| ```kotlin |
| fun add(a: Int, b: Int = 0) { |
| print("a + b = ${a + b}") |
| } |
| ``` |
| |
| Use the `defaultValue()` builder function to declare default value for a function argument. |
| |
| ```kotlin |
| FunSpec.builder("add") |
| .addParameter("a", Int::class) |
| .addParameter( |
| ParameterSpec.builder("b", Int::class) |
| .defaultValue("%L", 0) |
| .build() |
| ) |
| .addStatement("print(\"a + b = ${a + b}\")") |
| .build() |
| ``` |
| |
| #### Spaces wrap by default! |
| |
| In order to provide meaningful formatting, KotlinPoet would replace spaces, found in blocks of code, |
| with new line symbols, in cases when the line of code exceeds the length limit. Let's take this |
| function for example: |
| |
| ```kotlin |
| val funSpec = FunSpec.builder("foo") |
| .addStatement("return (100..10000).map { number -> number * number }.map { number -> number.toString() }.also { string -> println(string) }") |
| .build() |
| ``` |
| |
| Depending on where it's found in the file, it may end up being printed out like this: |
| |
| ```kotlin |
| fun foo() = (100..10000).map { number -> number * number }.map { number -> number.toString() }.also |
| { string -> println(string) } |
| ``` |
| |
| Unfortunately this code is broken: the compiler expects `also` and `{` to be on the same line. |
| KotlinPoet is unable to understand the context of the expression and fix the formatting for you, but |
| there's a trick you can use to declare a non-breaking space - use the `·` symbol where you would |
| otherwise use a space. Let's apply this to our example: |
| |
| ```kotlin |
| val funSpec = FunSpec.builder("foo") |
| .addStatement("return (100..10000).map·{ number -> number * number }.map·{ number -> number.toString() }.also·{ string -> println(string) }") |
| .build() |
| ``` |
| |
| This will now produce the following result: |
| |
| ```kotlin |
| fun foo() = (100..10000).map { number -> number * number }.map { number -> |
| number.toString() |
| }.also { string -> println(string) } |
| ``` |
| |
| The code is now correct and will compile properly. It still doesn't look perfect - you can play with |
| replacing other spaces in the code block with `·` symbols to achieve better formatting. |
| |
| Another common use case where you'd want to ensure spaces don't wrap is when emitting string literals: |
| |
| ```kotlin |
| CodeBlock.of("""println("Class: $className")""") |
| ``` |
| |
| If `$className` is long, KotlinPoet may wrap the space that precedes it, resulting in broken output: |
| |
| ```kotlin |
| println("Class: |
| very.long.class.name.Here") |
| ``` |
| |
| KotlinPoet doesn't know that `"Class: $className"` is, in fact, a string literal, and that the space inside of it |
| should never be wrapped. To make sure this case is handled correctly, use the `%S` modifier (as described in |
| [%S for Strings](#s-for-strings)): |
| |
| ```kotlin |
| CodeBlock.of("""println(%S)""", "Class: $className") |
| ``` |
| |
| Now the library knows it's dealing with a string literal and can use appropriate line-wrapping rules. |
| |
| ### Constructors |
| |
| `FunSpec` is a slight misnomer; it can also be used for constructors: |
| |
| ```kotlin |
| val flux = FunSpec.constructorBuilder() |
| .addParameter("greeting", String::class) |
| .addStatement("this.%N = %N", "greeting", "greeting") |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addProperty("greeting", String::class, KModifier.PRIVATE) |
| .addFunction(flux) |
| .build() |
| ``` |
| |
| Which generates this: |
| |
| ```kotlin |
| class HelloWorld { |
| private val greeting: String |
| |
| constructor(greeting: String) { |
| this.greeting = greeting |
| } |
| } |
| ``` |
| |
| For the most part, constructors work just like methods. When emitting code, KotlinPoet will place |
| constructors before methods in the output file. |
| |
| Often times you'll need to generate the primary constructor for a class: |
| |
| ```kotlin |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .primaryConstructor(flux) |
| .addProperty("greeting", String::class, KModifier.PRIVATE) |
| .build() |
| ``` |
| |
| This code, however, generates the following: |
| |
| ```kotlin |
| class HelloWorld(greeting: String) { |
| private val greeting: String |
| |
| init { |
| this.greeting = greeting |
| } |
| } |
| ``` |
| |
| By default, KotlinPoet won't merge primary constructor parameters and properties, even if they share |
| the same name. To achieve the effect, you have to tell KotlinPoet that the property is initialized |
| via the constructor parameter: |
| |
| ```kotlin |
| val flux = FunSpec.constructorBuilder() |
| .addParameter("greeting", String::class) |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .primaryConstructor(flux) |
| .addProperty( |
| PropertySpec.builder("greeting", String::class) |
| .initializer("greeting") |
| .addModifiers(KModifier.PRIVATE) |
| .build() |
| ) |
| .build() |
| ``` |
| |
| Now we're getting the following output: |
| |
| ```kotlin |
| class HelloWorld(private val greeting: String) |
| ``` |
| |
| Notice that KotlinPoet omits `{}` for classes with empty bodies. |
| |
| ### Parameters |
| |
| Declare parameters on methods and constructors with either `ParameterSpec.builder()` or |
| `FunSpec`'s convenient `addParameter()` API: |
| |
| ```kotlin |
| val android = ParameterSpec.builder("android", String::class) |
| .defaultValue("\"pie\"") |
| .build() |
| |
| val welcomeOverlords = FunSpec.builder("welcomeOverlords") |
| .addParameter(android) |
| .addParameter("robot", String::class) |
| .build() |
| ``` |
| |
| The code above generates: |
| |
| ```kotlin |
| fun welcomeOverlords(android: String = "pie", robot: String) { |
| } |
| ``` |
| |
| The extended `Builder` form is necessary when the parameter has annotations (such as `@Inject`). |
| |
| ### Properties |
| |
| Like parameters, properties can be created either with builders or by using convenient helper |
| methods: |
| |
| ```kotlin |
| val android = PropertySpec.builder("android", String::class) |
| .addModifiers(KModifier.PRIVATE) |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addProperty(android) |
| .addProperty("robot", String::class, KModifier.PRIVATE) |
| .build() |
| ``` |
| |
| Which generates: |
| |
| ```kotlin |
| class HelloWorld { |
| private val android: String |
| |
| private val robot: String |
| } |
| ``` |
| |
| The extended `Builder` form is necessary when a field has KDoc, annotations, or a field |
| initializer. Field initializers use the same [`String.format()`][formatter]-like syntax as the code |
| blocks above: |
| |
| ```kotlin |
| val android = PropertySpec.builder("android", String::class) |
| .addModifiers(KModifier.PRIVATE) |
| .initializer("%S + %L", "Oreo v.", 8.1) |
| .build() |
| ``` |
| |
| Which generates: |
| |
| ```kotlin |
| private val android: String = "Oreo v." + 8.1 |
| ``` |
| |
| By default `PropertySpec.Builder` produces `val` properties. Use `mutable()` if you need a |
| `var`: |
| |
| ```kotlin |
| val android = PropertySpec.builder("android", String::class) |
| .mutable() |
| .addModifiers(KModifier.PRIVATE) |
| .initializer("%S + %L", "Oreo v.", 8.1) |
| .build() |
| ``` |
| |
| #### Inline properties |
| |
| The way KotlinPoet models inline properties deserves special mention. The following snippet of code: |
| |
| ```kotlin |
| val android = PropertySpec.builder("android", String::class) |
| .mutable() |
| .addModifiers(KModifier.INLINE) |
| .build() |
| ``` |
| |
| will produce an error: |
| |
| ``` |
| java.lang.IllegalArgumentException: KotlinPoet doesn't allow setting the inline modifier on |
| properties. You should mark either the getter, the setter, or both inline. |
| ``` |
| |
| Indeed, a property marked with `inline` should have at least one accessor which will be inlined by |
| the compiler. Let's add a getter to this property: |
| |
| ```kotlin |
| val android = PropertySpec.builder("android", String::class) |
| .mutable() |
| .getter( |
| FunSpec.getterBuilder() |
| .addModifiers(KModifier.INLINE) |
| .addStatement("return %S", "foo") |
| .build() |
| ) |
| .build() |
| ``` |
| |
| The result is the following: |
| |
| ```kotlin |
| var android: kotlin.String |
| inline get() = "foo" |
| ``` |
| |
| Now, what if we wanted to add a non-inline setter to the property above? We can do so without |
| modifying any of the code we wrote previously: |
| |
| ```kotlin |
| val android = PropertySpec.builder("android", String::class) |
| .mutable() |
| .getter( |
| FunSpec.getterBuilder() |
| .addModifiers(KModifier.INLINE) |
| .addStatement("return %S", "foo") |
| .build() |
| ) |
| .setter( |
| FunSpec.setterBuilder() |
| .addParameter("value", String::class) |
| .build() |
| ) |
| .build() |
| ``` |
| |
| We get the expected result: |
| |
| ```kotlin |
| var android: kotlin.String |
| inline get() = "foo" |
| set(`value`) { |
| } |
| ``` |
| |
| Finally, if we go back and add `KModifier.INLINE` to the setter, KotlinPoet can wrap it nicely and |
| produce the following result: |
| |
| ```kotlin |
| inline var android: kotlin.String |
| get() = "foo" |
| set(`value`) { |
| } |
| ``` |
| |
| Removing the modifier from either the getter or the setter will unwrap the expression back. |
| |
| If, on the other hand, KotlinPoet had allowed marking a property `inline` directly, the programmer |
| would have had to manually add/remove the modifier whenever the state of the accessors changes in |
| order to get correct and compilable output. We're solving this problem by making accessors the |
| source of truth for the `inline` modifier. |
| |
| ### Interfaces |
| |
| KotlinPoet has no trouble with interfaces. Note that interface methods must always be `ABSTRACT`. |
| The modifier is necessary when defining the interface: |
| |
| ```kotlin |
| val helloWorld = TypeSpec.interfaceBuilder("HelloWorld") |
| .addProperty("buzz", String::class) |
| .addFunction( |
| FunSpec.builder("beep") |
| .addModifiers(KModifier.ABSTRACT) |
| .build() |
| ) |
| .build() |
| ``` |
| |
| But these modifiers are omitted when the code is generated. These are the default so we don't need |
| to include them for `kotlinc`'s benefit! |
| |
| ```kotlin |
| interface HelloWorld { |
| val buzz: String |
| |
| fun beep() |
| } |
| ``` |
| |
| Kotlin 1.4 adds support for functional interfaces via `fun interface` syntax. To create this in |
| KotlinPoet, use `TypeSpec.funInterfaceBuilder()`. |
| |
| ```kotlin |
| val helloWorld = TypeSpec.funInterfaceBuilder("HelloWorld") |
| .addFunction( |
| FunSpec.builder("beep") |
| .addModifiers(KModifier.ABSTRACT) |
| .build() |
| ) |
| .build() |
| |
| // Generates... |
| fun interface HelloWorld { |
| fun beep() |
| } |
| ``` |
| |
| ### Objects |
| |
| KotlinPoet supports objects: |
| |
| ```kotlin |
| val helloWorld = TypeSpec.objectBuilder("HelloWorld") |
| .addProperty( |
| PropertySpec.builder("buzz", String::class) |
| .initializer("%S", "buzz") |
| .build() |
| ) |
| .addFunction( |
| FunSpec.builder("beep") |
| .addStatement("println(%S)", "Beep!") |
| .build() |
| ) |
| .build() |
| ``` |
| |
| Similarly, you can create companion objects and add them to classes using `addType()`: |
| |
| ```kotlin |
| val companion = TypeSpec.companionObjectBuilder() |
| .addProperty( |
| PropertySpec.builder("buzz", String::class) |
| .initializer("%S", "buzz") |
| .build() |
| ) |
| .addFunction( |
| FunSpec.builder("beep") |
| .addStatement("println(%S)", "Beep!") |
| .build() |
| ) |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addType(companion) |
| .build() |
| ``` |
| |
| You can provide an optional name for a companion object. |
| |
| ### Enums |
| |
| Use `enumBuilder` to create the enum type, and `addEnumConstant()` for each value: |
| |
| ```kotlin |
| val helloWorld = TypeSpec.enumBuilder("Roshambo") |
| .addEnumConstant("ROCK") |
| .addEnumConstant("SCISSORS") |
| .addEnumConstant("PAPER") |
| .build() |
| ``` |
| |
| To generate this: |
| |
| ```kotlin |
| enum class Roshambo { |
| ROCK, |
| |
| SCISSORS, |
| |
| PAPER |
| } |
| ``` |
| |
| Fancy enums are supported, where the enum values override methods or call a superclass constructor. |
| Here's a comprehensive example: |
| |
| ```kotlin |
| val helloWorld = TypeSpec.enumBuilder("Roshambo") |
| .primaryConstructor( |
| FunSpec.constructorBuilder() |
| .addParameter("handsign", String::class) |
| .build() |
| ) |
| .addEnumConstant( |
| "ROCK", TypeSpec.anonymousClassBuilder() |
| .addSuperclassConstructorParameter("%S", "fist") |
| .addFunction( |
| FunSpec.builder("toString") |
| .addModifiers(KModifier.OVERRIDE) |
| .addStatement("return %S", "avalanche!") |
| .returns(String::class) |
| .build() |
| ) |
| .build() |
| ) |
| .addEnumConstant( |
| "SCISSORS", TypeSpec.anonymousClassBuilder() |
| .addSuperclassConstructorParameter("%S", "peace") |
| .build() |
| ) |
| .addEnumConstant( |
| "PAPER", TypeSpec.anonymousClassBuilder() |
| .addSuperclassConstructorParameter("%S", "flat") |
| .build() |
| ) |
| .addProperty( |
| PropertySpec.builder("handsign", String::class, KModifier.PRIVATE) |
| .initializer("handsign") |
| .build() |
| ) |
| .build() |
| ``` |
| |
| Which generates this: |
| |
| ```kotlin |
| enum class Roshambo(private val handsign: String) { |
| ROCK("fist") { |
| override fun toString(): String = "avalanche!" |
| }, |
| |
| SCISSORS("peace"), |
| |
| PAPER("flat"); |
| } |
| ``` |
| |
| ### Anonymous Inner Classes |
| |
| In the enum code, we used `TypeSpec.anonymousClassBuilder()`. Anonymous inner classes can also be |
| used in code blocks. They are values that can be referenced with `%L`: |
| |
| ```kotlin |
| val comparator = TypeSpec.anonymousClassBuilder() |
| .addSuperinterface(Comparator::class.parameterizedBy(String::class)) |
| .addFunction( |
| FunSpec.builder("compare") |
| .addModifiers(KModifier.OVERRIDE) |
| .addParameter("a", String::class) |
| .addParameter("b", String::class) |
| .returns(Int::class) |
| .addStatement("return %N.length - %N.length", "a", "b") |
| .build() |
| ) |
| .build() |
| |
| val helloWorld = TypeSpec.classBuilder("HelloWorld") |
| .addFunction( |
| FunSpec.builder("sortByLength") |
| .addParameter("strings", List::class.parameterizedBy(String::class)) |
| .addStatement("%N.sortedWith(%L)", "strings", comparator) |
| .build() |
| ) |
| .build() |
| ``` |
| |
| This generates a method that contains a class that contains a method: |
| |
| ```kotlin |
| class HelloWorld { |
| fun sortByLength(strings: List<String>) { |
| strings.sortedWith(object : Comparator<String> { |
| override fun compare(a: String, b: String): Int = a.length - b.length |
| }) |
| } |
| } |
| ``` |
| |
| One particularly tricky part of defining anonymous inner classes is the arguments to the superclass |
| constructor. To pass them use `TypeSpec.Builder`'s `addSuperclassConstructorParameter()` method. |
| |
| ### Annotations |
| |
| Simple annotations are easy: |
| |
| ```kotlin |
| val test = FunSpec.builder("test string equality") |
| .addAnnotation(Test::class) |
| .addStatement("assertThat(%1S).isEqualTo(%1S)", "foo") |
| .build() |
| ``` |
| |
| Which generates this function with an `@Test` annotation: |
| |
| ```kotlin |
| @Test |
| fun `test string equality`() { |
| assertThat("foo").isEqualTo("foo") |
| } |
| ``` |
| |
| Use `AnnotationSpec.builder()` to set properties on annotations: |
| |
| ```kotlin |
| val logRecord = FunSpec.builder("recordEvent") |
| .addModifiers(KModifier.ABSTRACT) |
| .addAnnotation( |
| AnnotationSpec.builder(Headers::class) |
| .addMember("accept = %S", "application/json; charset=utf-8") |
| .addMember("userAgent = %S", "Square Cash") |
| .build() |
| ) |
| .addParameter("logRecord", LogRecord::class) |
| .returns(LogReceipt::class) |
| .build() |
| ``` |
| |
| Which generates this annotation with `accept` and `userAgent` properties: |
| |
| ```kotlin |
| @Headers( |
| accept = "application/json; charset=utf-8", |
| userAgent = "Square Cash" |
| ) |
| abstract fun recordEvent(logRecord: LogRecord): LogReceipt |
| ``` |
| |
| When you get fancy, annotation values can be annotations themselves. Use `%L` for embedded |
| annotations: |
| |
| ```kotlin |
| val headerList = ClassName("", "HeaderList") |
| val header = ClassName("", "Header") |
| val logRecord = FunSpec.builder("recordEvent") |
| .addModifiers(KModifier.ABSTRACT) |
| .addAnnotation( |
| AnnotationSpec.builder(headerList) |
| .addMember( |
| "[\n⇥%L,\n%L⇤\n]", |
| AnnotationSpec.builder(header) |
| .addMember("name = %S", "Accept") |
| .addMember("value = %S", "application/json; charset=utf-8") |
| .build(), |
| AnnotationSpec.builder(header) |
| .addMember("name = %S", "User-Agent") |
| .addMember("value = %S", "Square Cash") |
| .build() |
| ) |
| .build() |
| ) |
| .addParameter("logRecord", logRecordName) |
| .returns(logReceipt) |
| .build() |
| ``` |
| |
| Which generates this: |
| |
| ```kotlin |
| @HeaderList( |
| [ |
| Header(name = "Accept", value = "application/json; charset=utf-8"), |
| Header(name = "User-Agent", value = "Square Cash") |
| ] |
| ) |
| abstract fun recordEvent(logRecord: LogRecord): LogReceipt |
| ``` |
| |
| KotlinPoet supports use-site targets for annotations: |
| |
| ```kotlin |
| val utils = FileSpec.builder("com.example", "Utils") |
| .addAnnotation( |
| AnnotationSpec.builder(JvmName::class) |
| .useSiteTarget(UseSiteTarget.FILE) |
| .build() |
| ) |
| .addFunction( |
| FunSpec.builder("abs") |
| .receiver(Int::class) |
| .returns(Int::class) |
| .addStatement("return if (this < 0) -this else this") |
| .build() |
| ) |
| .build() |
| ``` |
| |
| Will output this: |
| |
| ```kotlin |
| @file:JvmName |
| |
| package com.example |
| |
| import kotlin.Int |
| import kotlin.jvm.JvmName |
| |
| fun Int.abs(): Int = if (this < 0) -this else this |
| ``` |
| |
| ### Type Aliases |
| |
| KotlinPoet provides API for creating Type Aliases, which supports simple class names, parameterized |
| types and lambdas: |
| |
| ```kotlin |
| val k = TypeVariableName("K") |
| val t = TypeVariableName("T") |
| |
| val fileTable = Map::class.asClassName() |
| .parameterizedBy(k, Set::class.parameterizedBy(File::class)) |
| |
| val predicate = LambdaTypeName.get( |
| parameters = arrayOf(t), |
| returnType = Boolean::class.asClassName() |
| ) |
| val helloWorld = FileSpec.builder("com.example", "HelloWorld") |
| .addTypeAlias(TypeAliasSpec.builder("Word", String::class).build()) |
| .addTypeAlias( |
| TypeAliasSpec.builder("FileTable", fileTable) |
| .addTypeVariable(k) |
| .build() |
| ) |
| .addTypeAlias( |
| TypeAliasSpec.builder("Predicate", predicate) |
| .addTypeVariable(t) |
| .build() |
| ) |
| .build() |
| ``` |
| |
| Which generates the following: |
| |
| ```kotlin |
| package com.example |
| |
| import java.io.File |
| import kotlin.Boolean |
| import kotlin.String |
| import kotlin.collections.Map |
| import kotlin.collections.Set |
| |
| typealias Word = String |
| |
| typealias FileTable<K> = Map<K, Set<File>> |
| |
| typealias Predicate<T> = (T) -> Boolean |
| ``` |
| |
| ### Callable References |
| |
| [Callable references](https://kotlinlang.org/docs/reference/reflection.html#callable-references) to |
| constructors, functions, and properties may be emitted via: |
| |
| - `ClassName.constructorReference()` for constructors |
| - `MemberName.reference()` for functions and properties |
| |
| For example, |
| |
| ```kotlin |
| val helloClass = ClassName("com.example.hello", "Hello") |
| val worldFunction: MemberName = helloClass.member("world") |
| val byeProperty: MemberName = helloClass.nestedClass("World").member("bye") |
| |
| val factoriesFun = FunSpec.builder("factories") |
| .addStatement("val hello = %L", helloClass.constructorReference()) |
| .addStatement("val world = %L", worldFunction.reference()) |
| .addStatement("val bye = %L", byeProperty.reference()) |
| .build() |
| |
| FileSpec.builder("com.example", "HelloWorld") |
| .addFunction(factoriesFun) |
| .build() |
| ``` |
| |
| would generate: |
| |
| ```kotlin |
| package com.example |
| |
| import com.example.hello.Hello |
| |
| fun factories() { |
| val hello = ::Hello |
| val world = Hello::world |
| val bye = Hello.World::bye |
| } |
| ``` |
| |
| Top-level classes and members with conflicting names may require aliased imports, as with |
| [member names](#m-for-members). |
| |
| kotlin-reflect |
| -------- |
| |
| To generate source code from |
| any [`KType`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-type/), including |
| information that's not accessible to the builtin reflection APIs, KotlinPoet depends |
| on [kotlin-reflect](https://kotlinlang.org/docs/reflection.html#jvm-dependency). `kotlin-reflect` |
| can read the metadata of your classes and access this extra information. KotlinPoet can for an |
| example, read the type parameters and |
| their [variance](https://kotlinlang.org/docs/generics.html#variance) from a generic `KType` and |
| generate appropriate source code. |
| |
| `kotlin-reflect` is a relatively big dependency though and in some cases it is desirable to remove |
| it from the final executable to save some space and/or simplify the proguard/R8 setup (for example |
| for a Gradle plugin that generates Kotlin code). It is possible to do so and still use most of the |
| KotlinPoet APIs: |
| |
| ```kotlin |
| dependencies { |
| implementation("com.squareup:kotlinpoet:<version>") { |
| exclude(module = "kotlin-reflect") |
| } |
| } |
| ``` |
| |
| The main APIs that require `kotlin-reflect` |
| are [`KType.asTypeName()`](https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/as-type-name.html) |
| and [`typeNameOf<T>()`](https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/type-name-of.html). |
| If you're calling one of these without `kotlin-reflect` in the classpath and the type is generic |
| or has annotations you will get a crash. |
| |
| You can replace it with code that passes type parameters or annotations explicitly and doesn't |
| need `kotlin-reflect`. For example: |
| |
| ```kotlin |
| // Replace |
| // kotlin-reflect needed |
| val typeName = typeNameOf<List<Int?>>() |
| |
| // With |
| // kotlin-reflect not needed |
| val typeName = |
| List::class.asClassName().parameterizedBy(Int::class.asClassName().copy(nullable = true)) |
| ``` |
| |
| Download |
| -------- |
| |
| ![Maven Central][version-shield] |
| |
| Download [the latest .jar][dl] or depend via Maven: |
| |
| ```xml |
| <dependency> |
| <groupId>com.squareup</groupId> |
| <artifactId>kotlinpoet</artifactId> |
| <version>[version]</version> |
| </dependency> |
| ``` |
| |
| or Gradle: |
| |
| ```groovy |
| implementation("com.squareup:kotlinpoet:[version]") |
| ``` |
| |
| Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. |
| |
| |
| License |
| ------- |
| |
| Copyright 2017 Square, Inc. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| https://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| |
| [dl]: https://search.maven.org/remote_content?g=com.squareup&a=kotlinpoet&v=LATEST |
| [version-shield]: https://img.shields.io/maven-central/v/com.squareup/kotlinpoet |
| [snap]: https://s01.oss.sonatype.org/content/repositories/snapshots/com/squareup/kotlinpoet/ |
| [kdoc]: https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/ |
| [javapoet]: https://github.com/square/javapoet/ |
| [formatter]: https://developer.android.com/reference/java/util/Formatter.html |