| name | codegen |
| description | Documents the Fakt code generation pipeline — immutable AST model, builder DSL, renderer, default value strategies, and extension functions. Use when modifying code generation, changing generated output, adding new generated patterns, understanding builders or renderers, adding a new default value strategy, or debugging generated output. Make sure to use this skill whenever generated Kotlin code needs to change — the pipeline has strict conventions (immutable AST, builder DSL, renderer separation) that must be followed even for small changes. |
| allowed-tools | Read, Grep, Glob |
Codegen Pipeline Guide
The codegen system transforms IR analysis into Kotlin source files through a layered pipeline: Model (immutable AST) → Builder (DSL construction) → Renderer (string output).
Instructions
1. Understand the Pipeline
InterfaceAnalysis / ClassAnalysis (from IR phase)
↓
AnalysisToCodegenMapper (IR → MethodSpec/PropertySpec)
↓
FakeGenerator / ConfigurationDslGenerator (orchestrators)
↓
codeFile("pkg") { klass("...") { ... } } (Builder DSL)
↓
CodeFile (immutable AST) (Model)
↓
CodeFile.renderTo(CodeBuilder) (Renderer)
↓
Kotlin source string (written to test source set)
All codegen files live under compiler/src/main/kotlin/com/rsicarelli/fakt/codegen/.
2. Model Layer — codegen/model/CodeFile.kt
Single file, fully immutable value objects. Key types:
| Type | Purpose |
|---|
CodeFile | Root: package + imports + declarations |
CodeDeclaration | Sealed: CodeClass, CodeDataClass, CodeFunction, CodeProperty |
CodeMember | Sealed: class body items (CodeFunction, CodeProperty, CodeComment, CodeRegionStart/End) |
CodeClass | Class with modifiers, supertypes, members, constructor properties |
CodeDataClass | Data class with typed properties |
CodeFunction | Function with params, return type, body |
CodeProperty | Property with initializer, getter/setter |
CodeType | Sealed: Simple, Generic, Nullable, Lambda |
CodeBlock | Sealed: Expression, Statements, Empty |
CodeExpression | Sealed: StringLiteral, NumberLiteral, Lambda, FunctionCall, Raw |
CodeModifier | Enum: PUBLIC, PRIVATE, INTERNAL, OVERRIDE, SUSPEND, INLINE, etc. |
ConstructorProperty | Constructor parameter with optional default value |
3. Builder Layer — codegen/builder/
Entry point: codeFile(packageName) { } — returns CodeFile. All builders use @CodeDsl marker.
CodeFileBuilder (top-level):
codeFile("com.example") {
header = "Generated by Fakt"
import("kotlinx.coroutines.flow.StateFlow")
klass("FakeImpl") { }
function("fakeFactory") { }
dataClass("CallData") { }
}
ClassBuilder — klass(name) { }:
klass("FakeUserServiceImpl") {
public(); internal()
typeParam("T", "Comparable<T>")
implements("UserService")
implements("Repository", "User")
annotation("OptIn", "ExperimentalApi::class")
constructorProperty("getBehavior", "(String) -> User?") { private(); defaultValue = "null" }
region("Properties")
property("count", "Int") { private(); mutable(); initializer = "0" }
function("getUser") { override(); returns("User?"); expressionBody = "getBehavior(id)" }
endRegion()
}
FunctionBuilder — function(name) { }:
function("getUser") {
override(); suspend(); inline(); operator()
receiver("MyType")
typeParam("T", "Comparable<T>", reified = true)
parameter("id", "String")
parameter("count", "Int", defaultValue = "0")
parameter("items", "String", vararg = true)
returns("User?")
compact = true
body = "return getBehavior(id)"
expressionBody = "getBehavior(id)"
body { buildString { } }
}
PropertyBuilder — property(name, type) { }:
property("name", "String") {
override(); private(); mutable()
compact = true
initializer = "\"default\""
getter = "field.uppercase()"
setter = "field = value"
}
DataClassBuilder — dataClass(name) { }:
dataClass("GetUserCall") {
public()
property("id", "String")
property("includeDeleted", "Boolean")
}
4. Renderer Layer — codegen/renderer/
CodeBuilder — StringBuilder wrapper with indentation:
val cb = CodeBuilder(indentSize = 4)
cb.appendLine("val x = 1")
cb.indent { appendLine("inner") }
cb.block("class Foo") { ... }
cb.build()
Rendering.kt — extension functions: CodeFile.renderTo(builder), CodeFile.renderToString(), and render functions for each model type. Key behaviors:
Unit return type omitted on expression bodies
- KDoc rendered before annotations
- Compact siblings skip blank lines between them
- Multi-line constructor properties when > 1
- Package segments with Kotlin keywords get backtick-escaped
5. Strategy Layer — codegen/strategy/
DefaultValueResolver — composes strategies in priority order:
| Priority | Strategy | Handles |
|---|
| 1 | NullableDefaultStrategy | T? → null |
| 2 | PrimitiveDefaultStrategy | String → "", Int → 0, Boolean → false, etc. |
| 3 | StdlibDefaultStrategy | Unit, Flow<T> → emptyFlow(), StateFlow<T> → MutableStateFlow(...), Result<T> |
| 4 | CollectionDefaultStrategy | List<T> → emptyList(), Map<K,V> → emptyMap(), Array<T>, Mutable* |
| fallback | error | "Type '...' requires explicit configuration..." |
To add a new default strategy: implement DefaultValueStrategy interface (supports() + defaultValue()), add to the list in DefaultValueResolver.
6. Extensions Layer — codegen/extensions/
Spec types bridging IR analysis to builders:
| Spec | Purpose |
|---|
MethodSpec | Method metadata: name, params, return type, suspend, operator, default behavior |
PropertySpec | Property metadata: name, type, isStateFlow, isMutable, default behavior |
AnnotationSpec | Annotation with OptIn detection |
FactoryFunctionSpec | Factory function metadata for fakeXxx {} generation |
Extension functions on builders (in separate files):
| File | Functions | Purpose |
|---|
FakeGenerator.kt | generateCompleteFake() | Main fake class generation |
MethodExtensions.kt | overrideMethod(), overrideVarargMethod(), configureMethod() | Method overrides + configure methods |
PropertyExtensions.kt | stateFlowProperty(), behaviorProperty(), suspendBehaviorProperty(), nullableBehaviorProperty(), nullableSuspendBehaviorProperty() | Property overrides + behavior lambdas |
CallTrackingExtensions.kt | callHistoryBackingField(), propertyGetterCallHistoryField(), propertySetterCallHistoryField() | Call tracking fields |
CallHistoryDsl.kt | callDataClass(), verifierClass(), unitVerifierClass(), verifyFunction(), unitVerifyFunction() | Call history data classes + verifiers |
CallHistoryGenerator.kt | addCallHistoryComponents(), generateCallHistoryDeclarations() | Orchestrates all call history code |
FactoryExtensions.kt | generateFactoryFunctionCodeFile() | fakeXxx {} factory function |
TypeParamRegex.kt | eraseTypeParamsToAny(), typeContainsAnyParam() | Generic type erasure utilities |
HistoryStorageInfo.kt | resolveHistoryStorageType() | Resolves call history storage (unit vs data class) |
KDocGenerator.kt | KDoc generation utilities | Generated documentation for fakes |
TypeParamsInfo.kt | Type parameter analysis helpers | Type parameter metadata extraction |
VisibilityExtensions.kt | Visibility mapping helpers | FIR visibility to codegen modifier mapping |
7. Generation Orchestrators — compiler/ir/generation/
| File | Class | Role |
|---|
AnalysisToCodegenMapper.kt | — | FunctionAnalysis.toMethodSpec(), PropertyAnalysis.toPropertySpec() |
ImplementationGenerator.kt | ImplementationGenerator | Produces FakeXxxImpl class + factory function + call history |
ConfigurationDslGenerator.kt | ConfigurationDslGenerator | Produces FakeXxxConfig DSL class |
ImplementationGenerator.generateImplementation() flow:
analysis.toCodegenSpecs() → (List<MethodSpec>, List<PropertySpec>)
generateCompleteFake() → CodeFile (impl class)
generateFactoryFunctionCodeFile() → CodeFile (factory)
generateCallHistoryDeclarations() → List<CodeDeclaration> (appended to impl file)
- Returns
GeneratedFakeCode(implementationFile, factoryFunction)
8. Adding a New Generation Pattern
- Define the output — what Kotlin code should be generated
- Create spec (if needed) — add to
codegen/extensions/ or extend existing specs
- Add builder extensions — new functions on
ClassBuilder or CodeFileBuilder
- Wire into orchestrator — call from
ImplementationGenerator or ConfigurationDslGenerator
- Add default strategy (if needed) — implement
DefaultValueStrategy, add to DefaultValueResolver
- Test — verify with
make publish-local && make test-sample
Related Skills
feature-option — adding options that conditionally enable/disable generation
compiler-architecture-validator — validate FIR/IR separation
interface-analyzer — understanding what gets analyzed before codegen