Code generation approach for TypeScript types
Proposal
Generate the TypeScript types with a small, indentation-aware CodeWriter written in Kotlin, instead of using a third-party
code generation library or template engine.
Background
The generated code only uses a few TypeScript constructs:
export type X = { ... }; with object types
- Unions of string literals, and
Array<...>
- JSDoc comments
import("...") type references
declare global { ... } for the global maps
The current renderers build this with string templates and trimMargin, and they pass the indentation level through
every function. Most of the complexity comes from managing whitespace and indentation, not from TypeScript itself.
The snapshot tests in src/test/snapshots require the output to stay byte for byte the same after the rewrite. That
makes full control over formatting a hard requirement.
Suggested design
A small writer that keeps track of indentation, so renderers never compute indentation themselves:
class CodeWriter {
private val out = StringBuilder()
private var level = 0
fun line(text: String = "") = apply {
out.append(if (text.isEmpty()) "" else " ".repeat(level) + text).append('\n')
}
fun block(open: String, close: String, body: CodeWriter.() -> Unit) = apply {
line(open); level++; body(); level--; line(close)
}
override fun toString() = out.toString()
}
Small helpers on top of it, e.g. comment(), property(), objectType() and union(), can each render one construct.
Nested item sets and option sets then just call block() recursively.
Why not a library?
- TypeScript generators from JVM classes (typescript-generator,
kotlinx-serialization-typescript-generator,
ts-generator, jvm2dts) generate types
from existing Java/Kotlin classes. Our types are only known at runtime, from the XP schemas, so these don't fit.
- typescriptpoet is a builder API for TypeScript, but its last release
was in 2021. It also formats the output its own way, which would make it hard to keep the output unchanged.
- Template engines (StringTemplate 4, JTE,
Pebble, JMustache) would work.
They add a dependency, though, and JTE, Pebble and JMustache leave indentation of nested structures to us, which is the
part we want to simplify. StringTemplate 4 does indent nested templates, but it hasn't had a release since 2022.
Benefits
- Full control over the output, so the snapshot tests keep passing through the rewrite.
- No extra dependency. The plugin runs inside users' Gradle builds, where every dependency risks conflicting with
other plugins.
- Simpler renderers. No
trimMargin, and no indentation levels passed around.
- Easy to unit test, since the writer is independent of the XML/YAML parsing.
If we later need something the writer can't handle well, StringTemplate 4 or JTE are the fallback options.
Code generation approach for TypeScript types
Proposal
Generate the TypeScript types with a small, indentation-aware
CodeWriterwritten in Kotlin, instead of using a third-partycode generation library or template engine.
Background
The generated code only uses a few TypeScript constructs:
export type X = { ... };with object typesArray<...>import("...")type referencesdeclare global { ... }for the global mapsThe current renderers build this with string templates and
trimMargin, and they pass the indentation level throughevery function. Most of the complexity comes from managing whitespace and indentation, not from TypeScript itself.
The snapshot tests in
src/test/snapshotsrequire the output to stay byte for byte the same after the rewrite. Thatmakes full control over formatting a hard requirement.
Suggested design
A small writer that keeps track of indentation, so renderers never compute indentation themselves:
Small helpers on top of it, e.g.
comment(),property(),objectType()andunion(), can each render one construct.Nested item sets and option sets then just call
block()recursively.Why not a library?
kotlinx-serialization-typescript-generator,
ts-generator, jvm2dts) generate types
from existing Java/Kotlin classes. Our types are only known at runtime, from the XP schemas, so these don't fit.
was in 2021. It also formats the output its own way, which would make it hard to keep the output unchanged.
Pebble, JMustache) would work.
They add a dependency, though, and JTE, Pebble and JMustache leave indentation of nested structures to us, which is the
part we want to simplify. StringTemplate 4 does indent nested templates, but it hasn't had a release since 2022.
Benefits
other plugins.
trimMargin, and no indentation levels passed around.If we later need something the writer can't handle well, StringTemplate 4 or JTE are the fallback options.