Skip to content

Commit

Permalink
Split KotlinPoet docs into multiple pages
Browse files Browse the repository at this point in the history
  • Loading branch information
Egorand committed Jan 9, 2024
1 parent 4593621 commit 44af27a
Show file tree
Hide file tree
Showing 23 changed files with 1,503 additions and 1,475 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ classes

# Mkdocs files
docs/1.x/*
site

obj

Expand Down
118 changes: 118 additions & 0 deletions docs/annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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
```
44 changes: 44 additions & 0 deletions docs/anonymous-inner-classes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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.
45 changes: 45 additions & 0 deletions docs/callable-references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Callable References
===================

[Callable references][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.md).

[callable-references]: https://kotlinlang.org/docs/reference/reflection.html#callable-references
36 changes: 36 additions & 0 deletions docs/code-block-format-strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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)
```
76 changes: 76 additions & 0 deletions docs/code-control-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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.
Loading

0 comments on commit 44af27a

Please sign in to comment.