Skip to content

Commit 5390da6

Browse files
committed
minor(emitter-zod): add options to transform schema declaration names
1 parent b3e67b9 commit 5390da6

File tree

6 files changed

+82
-16
lines changed

6 files changed

+82
-16
lines changed

.changeset/eight-coins-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@typespec-tools/emitter-zod": minor
3+
---
4+
5+
add options to transform schema declaration names

packages/emitter-zod/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ options:
5151
5252
### Emitter options
5353
54-
| Option | Type | Default | Description |
55-
| ------------- | ------ | ----------- | ----------------------- |
56-
| `output-file` | string | "output.ts" | Name of the output file |
54+
| Option | Type | Default | Description |
55+
| --------------- | ------ | ----------- | ------------------------------------------------------------------------------------------ |
56+
| `output-file` | string | "output.ts" | Name of the output file |
57+
| `schema-prefix` | string | "" | Prefix for schema declarations, i.e. a setting of "s\_" would result in "s_Example" |
58+
| `schema-suffix` | string | "Schema" | Suffix for schema declarations, i.e. a setting of "Schema" would result in "ExampleSchema" |

packages/emitter-zod/src/emitter.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ export const intrinsicNameToTSType = new Map<string, string>([
5757
export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
5858
protected nsByName: Map<string, Scope<string>> = new Map();
5959

60+
getPrefix() {
61+
const options = this.emitter.getOptions();
62+
return options["schema-prefix"] ?? "";
63+
}
64+
getSuffix() {
65+
const options = this.emitter.getOptions();
66+
return options["schema-suffix"] ?? "Schema";
67+
}
68+
wrapIdentifier(name: string) {
69+
return `${this.getPrefix()}${name}${this.getSuffix()}`;
70+
}
71+
6072
emitNamespaces(scope: Scope<string>) {
6173
let res = "";
6274
for (const childScope of scope.childScopes) {
@@ -197,7 +209,7 @@ export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
197209

198210
return this.emitter.result.declaration(
199211
name,
200-
code`${commentCode}\nexport const ${name}Schema = z.object({
212+
code`${commentCode}\nexport const ${this.wrapIdentifier(name)} = z.object({
201213
${this.emitter.emitModelProperties(model)}
202214
})`
203215
);
@@ -238,7 +250,7 @@ export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
238250
): EmitterOutput<string> {
239251
return this.emitter.result.declaration(
240252
name,
241-
code`export const ${name}Schema = z.array(${this.emitter.emitTypeReference(elementType)});`
253+
code`export const ${this.wrapIdentifier(name)} = z.array(${this.emitter.emitTypeReference(elementType)});`
242254
);
243255
}
244256

@@ -257,7 +269,7 @@ export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
257269
const returnsOutput = code`.returns(${this.emitter.emitOperationReturnType(operation)})`;
258270
return this.emitter.result.declaration(
259271
name,
260-
code`export const ${name}Schema = z.function()${argsOutput}${returnsOutput}`
272+
code`export const ${this.wrapIdentifier(name)} = z.function()${argsOutput}${returnsOutput}`
261273
);
262274
}
263275

@@ -289,7 +301,7 @@ export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
289301
return this.emitter.result.declaration(
290302
name,
291303
code`
292-
export const ${name}Schema = z.object({
304+
export const ${this.wrapIdentifier(name)} = z.object({
293305
${this.emitter.emitInterfaceOperations(iface)}
294306
})
295307
`
@@ -330,7 +342,7 @@ export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
330342
unionDeclaration(union: Union, name: string): EmitterOutput<string> {
331343
return this.emitter.result.declaration(
332344
name,
333-
code`export const ${name}Schema = ${this.emitter.emitUnionVariants(union)}`
345+
code`export const ${this.wrapIdentifier(name)} = ${this.emitter.emitUnionVariants(union)}`
334346
);
335347
}
336348

@@ -391,9 +403,9 @@ export class ZodEmitter extends CodeTypeEmitter<EmitterOptions> {
391403
const basePath = pathDown.map((s) => s.name).join(".");
392404
return basePath
393405
? this.emitter.result.rawCode(
394-
`${basePath}.${targetDeclaration.name}Schema`
406+
`${basePath}.${this.wrapIdentifier(targetDeclaration.name)}`
395407
)
396-
: this.emitter.result.rawCode(`${targetDeclaration.name}Schema`);
408+
: this.emitter.result.rawCode(this.wrapIdentifier(targetDeclaration.name));
397409
}
398410

399411
async sourceFile(sourceFile: SourceFile<string>): Promise<EmittedSourceFile> {

packages/emitter-zod/src/lib.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import { createTypeSpecLibrary, JSONSchemaType } from "@typespec/compiler";
22

33
export interface EmitterOptions {
44
"output-file"?: string;
5+
"schema-prefix"?: string;
6+
"schema-suffix"?: string;
57
}
68

79
const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
810
type: "object",
911
additionalProperties: false,
1012
properties: {
1113
"output-file": { type: "string", nullable: true },
14+
"schema-prefix": { type: "string", nullable: true },
15+
"schema-suffix": { type: "string", nullable: true, default: "Schema" },
1216
},
1317
required: [],
1418
};

packages/emitter-zod/test/emitter.test.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { describe, it } from "vitest";
1111

1212
import { SingleFileZodEmitter, ZodEmitter } from "../src/emitter.js";
1313
import { emitTypeSpec, getHostForTypeSpecFile } from "./host.js";
14+
import { EmitterOptions } from "../src/lib.js";
1415

1516
const testCode = `
1617
model Basic { x: string }
@@ -63,8 +64,8 @@ class SingleFileTestEmitter extends SingleFileZodEmitter {
6364
}
6465
}
6566

66-
async function emitTypeSpecToTs(code: string) {
67-
const emitter = await emitTypeSpec(SingleFileTestEmitter, code);
67+
async function emitTypeSpecToTs(code: string, options?: EmitterOptions) {
68+
const emitter = await emitTypeSpec(SingleFileTestEmitter, code, options);
6869

6970
const sf = await emitter.getProgram().host.readFile("./tsp-output/output.ts");
7071
return sf.text;
@@ -434,6 +435,45 @@ describe("emitter-framework: zod emitter", () => {
434435
assert.match(contents, /y: z.number\(\)/);
435436
});
436437

438+
it("emits declaration with custom prefix", async () => {
439+
const contents = await emitTypeSpecToTs(
440+
`
441+
model Foo {
442+
x: string;
443+
}
444+
`,
445+
{ "schema-prefix": "My", "schema-suffix": "" }
446+
);
447+
448+
assert.match(contents, /export const MyFoo = z.object\(/);
449+
});
450+
451+
it("emits declaration with custom suffix", async () => {
452+
const contents = await emitTypeSpecToTs(
453+
`
454+
model Foo {
455+
x: string;
456+
}
457+
`,
458+
{ "schema-suffix": "Testing" }
459+
);
460+
461+
assert.match(contents, /export const FooTesting = z.object\(/);
462+
});
463+
464+
it("emits declaration with custom prefix and suffix", async () => {
465+
const contents = await emitTypeSpecToTs(
466+
`
467+
model Foo {
468+
x: string;
469+
}
470+
`,
471+
{ "schema-prefix": "My_", "schema-suffix": "_Schema" }
472+
);
473+
474+
assert.match(contents, /export const My_Foo_Schema = z.object\(/);
475+
});
476+
437477
it("emits models to a single file", async () => {
438478
const host = await getHostForTypeSpecFile(testCode);
439479
const emitter = createAssetEmitter(host.program, SingleFileTestEmitter, {

packages/emitter-zod/test/host.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createAssetEmitter } from "@typespec/compiler/emitter-framework";
22
import { createTestHost } from "@typespec/compiler/testing";
3+
import { EmitterOptions } from "../src/lib.js";
34

45
export async function getHostForTypeSpecFile(
56
contents: string,
@@ -17,13 +18,15 @@ export async function getHostForTypeSpecFile(
1718
return host;
1819
}
1920

20-
export async function emitTypeSpec(Emitter: any, code: string) {
21+
export async function emitTypeSpec(
22+
Emitter: any,
23+
code: string,
24+
options: EmitterOptions = {}
25+
) {
2126
const host = await getHostForTypeSpecFile(code);
2227
const emitter = createAssetEmitter(host.program, Emitter, {
2328
emitterOutputDir: "tsp-output",
24-
options: {
25-
fileName: "testing.ts",
26-
},
29+
options,
2730
} as any);
2831

2932
emitter.emitProgram();

0 commit comments

Comments
 (0)