Commit 2f30faf
committed
feat(zod): add AsSchemas mutation for Zod v4 codegen
Introduces a zod package whose only exported mutation, zod.AsSchemas,
rewrites every Interface and Alias in a *guts.Typescript into a
Zod v4 schema VariableStatement plus an inferred type alias. The
mutation also injects `import { z } from "zod"` so the generated file
is self-contained.
Composability is the point. AsSchemas slots into the existing pipeline
alongside the config mutations rather than maintaining a parallel
string-builder. The recommended order is:
ts.ApplyMutations(
config.EnumAsTypes,
config.SimplifyOmitEmpty,
zod.AsSchemas,
config.ExportTypes,
)
EnumAsTypes lowers Go enums to unions of literals; SimplifyOmitEmpty
drops the null half of optional fields; AsSchemas rewrites the
interfaces and aliases; ExportTypes adds `export` to the new
declarations. Other mutations that walk Interface or Alias (ReadOnly,
TrimEnumPrefix, etc.) must run before AsSchemas because the originals
are replaced.
Conversion rules implemented:
- Interface with no heritage: `const FooSchema = z.object({...})` plus
`type Foo = z.infer<typeof FooSchema>`.
- Interface with single-base heritage: `BaseSchema.extend({...})`
instead of z.object. Multiple bases panic since Zod has no multiple
inheritance.
- Alias whose Type is a union of string literals: `z.enum([...])`.
- Alias with any other Type: recursive exprToZod plus the inferred
type alias.
- Field types: z.string, z.number, z.boolean for keywords; z.array
for arrays; z.record(K, V) for Record<K, V>; bare references emit
the paired Schema identifier; T | null collapses to .nullable();
single-non-null-member unions unwrap; QuestionToken appends
.optional(); inline TypeLiteralNode emits z.object; intersections
fold into a left-associative chain of z.intersection.
- Self-references are wrapped in z.lazy((): z.ZodType => SelfSchema)
so the value-position reference does not fire before the binding
exists.
- Cross-package prefix on bindings.Identifier flows through to the
emitted schema and reference, matching the rest of the AST.
Testing:
- testdata/zod/types.go and golden.ts give a realistic end-to-end
fixture covering structs, embedded fields, enums, nullable pointers,
arrays, maps, and self-references. testdata/zod/zod.ts is the
regular guts TS output for the TestGeneration loop.
- zod/zod_test.go pins each conversion case in isolation, including
the QuestionToken plus nullable interaction, the single-member union
unwrap, the cross-package prefix passthrough, and the heritage
extend path.
- zod/zod_e2e_test.go drives the full pipeline against the testdata
and diffs the output against golden.ts. Run with -update to
regenerate after intentional changes.
Replaces the string-builder approach in #82 with an AST mutation that
composes with the rest of guts.
Generated by Coder Agents on behalf of @Emyrk.1 parent 7b8e1a8 commit 2f30faf
8 files changed
Lines changed: 955 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
0 commit comments