From 158e38f89b860b331bd6b146d08c2bdeb765f0fe Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Fri, 4 Sep 2026 22:44:24 -0700 Subject: [PATCH 1/2] fix(zod): count Unicode code points for string length --- .../TypeScriptZod/TypeScriptZodRenderer.ts | 8 +++++-- test/fixtures.ts | 5 +++- ...-codepoint-length.1.fail.minmaxlength.json | 1 + .../unicode-codepoint-length.1.json | 1 + ...-codepoint-length.2.fail.minmaxlength.json | 1 + .../unicode-codepoint-length.2.json | 1 + ...-codepoint-length.3.fail.minmaxlength.json | 1 + ...-codepoint-length.4.fail.minmaxlength.json | 1 + ...icode-codepoint-length.5.fail.pattern.json | 1 + .../unicode-codepoint-length.schema | 24 +++++++++++++++++++ test/languages.ts | 4 ++++ 11 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.1.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.2.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json create mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.schema diff --git a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts index 9b20eb1a0c..ba46eb3f63 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts @@ -114,8 +114,6 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { const pattern = patternForType(type); return [ "z.string()", - min === undefined ? "" : [".min(", String(min), ")"], - max === undefined ? "" : [".max(", String(max), ")"], pattern === undefined ? "" : [ @@ -123,6 +121,12 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { utf16StringEscape(pattern), '"))', ], + min === undefined + ? "" + : `.refine(value => Array.from(value).length >= ${min})`, + max === undefined + ? "" + : `.refine(value => Array.from(value).length <= ${max})`, ]; }; diff --git a/test/fixtures.ts b/test/fixtures.ts index 74693fc082..4ea5299475 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -809,7 +809,10 @@ class JSONSchemaFixture extends LanguageFixture { } getSamples(sources: string[]): { priority: Sample[]; others: Sample[] } { - const prioritySamples = testsInDir("test/inputs/schema/", "schema"); + const prioritySamples = testsInDir( + "test/inputs/schema/", + "schema", + ).concat(this.language.additionalSchemaFiles ?? []); const samples = samplesFromSources( sources, prioritySamples, diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json new file mode 100644 index 0000000000..e225d1c57e --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json @@ -0,0 +1 @@ +{"exact": "šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.1.json b/test/inputs/typescript-zod/unicode-codepoint-length.1.json new file mode 100644 index 0000000000..5c07a588dd --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.1.json @@ -0,0 +1 @@ +{"exact": "šŸ˜€šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json new file mode 100644 index 0000000000..c8c6591ef5 --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json @@ -0,0 +1 @@ +{"exact": "šŸ˜€šŸ˜€šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.2.json b/test/inputs/typescript-zod/unicode-codepoint-length.2.json new file mode 100644 index 0000000000..9a9dca1d26 --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.2.json @@ -0,0 +1 @@ +{"exact": "é", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json new file mode 100644 index 0000000000..8db7d970e7 --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json @@ -0,0 +1 @@ +{"exact": "šŸ˜€šŸ˜€", "minimum": "šŸ˜€", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json new file mode 100644 index 0000000000..8d6738c793 --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json @@ -0,0 +1 @@ +{"exact": "šŸ˜€šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€a"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json b/test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json new file mode 100644 index 0000000000..bc2f3d621a --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json @@ -0,0 +1 @@ +{"exact": "!!", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.schema b/test/inputs/typescript-zod/unicode-codepoint-length.schema new file mode 100644 index 0000000000..83c23969c4 --- /dev/null +++ b/test/inputs/typescript-zod/unicode-codepoint-length.schema @@ -0,0 +1,24 @@ +{ + "type": "object", + "properties": { + "exact": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "pattern": "^[^!]+$" + }, + "minimum": { + "type": "string", + "minLength": 2 + }, + "maximum": { + "type": "string", + "maxLength": 1 + } + }, + "required": [ + "exact", + "minimum", + "maximum" + ] +} diff --git a/test/languages.ts b/test/languages.ts index c26ce281c0..7c97fd404c 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -70,6 +70,7 @@ export interface Language { includeJSON?: string[]; skipMiscJSON: boolean; skipSchema: string[]; + additionalSchemaFiles?: string[]; rendererOptions: RendererOptions; quickTestRendererOptions: (RendererOptions | [string, RendererOptions])[]; sourceFiles?: string[]; @@ -1692,6 +1693,9 @@ export const TypeScriptZodLanguage: Language = { topLevel: "TopLevel", skipJSON: [], skipMiscJSON: false, + additionalSchemaFiles: [ + "test/inputs/typescript-zod/unicode-codepoint-length.schema", + ], skipSchema: [], rendererOptions: {}, quickTestRendererOptions: [], From c8d1ce770f252c79e9030db1dc60a293a6c98104 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Sat, 5 Sep 2026 09:58:52 -0700 Subject: [PATCH 2/2] test(zod): use shared Unicode length cases --- ...-codepoint-length.1.fail.minmaxlength.json | 1 - .../unicode-codepoint-length.1.json | 1 - ...-codepoint-length.2.fail.minmaxlength.json | 1 - .../unicode-codepoint-length.2.json | 1 - ...-codepoint-length.3.fail.minmaxlength.json | 1 - ...-codepoint-length.4.fail.minmaxlength.json | 1 - ...icode-codepoint-length.5.fail.pattern.json | 1 - .../unicode-codepoint-length.schema | 24 ------------------- test/languages.ts | 2 +- 9 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.1.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.2.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json delete mode 100644 test/inputs/typescript-zod/unicode-codepoint-length.schema diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json deleted file mode 100644 index e225d1c57e..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.1.fail.minmaxlength.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.1.json b/test/inputs/typescript-zod/unicode-codepoint-length.1.json deleted file mode 100644 index 5c07a588dd..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.1.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "šŸ˜€šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json deleted file mode 100644 index c8c6591ef5..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.2.fail.minmaxlength.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "šŸ˜€šŸ˜€šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.2.json b/test/inputs/typescript-zod/unicode-codepoint-length.2.json deleted file mode 100644 index 9a9dca1d26..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.2.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "é", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json deleted file mode 100644 index 8db7d970e7..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.3.fail.minmaxlength.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "šŸ˜€šŸ˜€", "minimum": "šŸ˜€", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json b/test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json deleted file mode 100644 index 8d6738c793..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.4.fail.minmaxlength.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "šŸ˜€šŸ˜€", "minimum": "šŸ˜€a", "maximum": "šŸ˜€a"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json b/test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json deleted file mode 100644 index bc2f3d621a..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.5.fail.pattern.json +++ /dev/null @@ -1 +0,0 @@ -{"exact": "!!", "minimum": "šŸ˜€a", "maximum": "šŸ˜€"} diff --git a/test/inputs/typescript-zod/unicode-codepoint-length.schema b/test/inputs/typescript-zod/unicode-codepoint-length.schema deleted file mode 100644 index 83c23969c4..0000000000 --- a/test/inputs/typescript-zod/unicode-codepoint-length.schema +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "object", - "properties": { - "exact": { - "type": "string", - "minLength": 2, - "maxLength": 2, - "pattern": "^[^!]+$" - }, - "minimum": { - "type": "string", - "minLength": 2 - }, - "maximum": { - "type": "string", - "maxLength": 1 - } - }, - "required": [ - "exact", - "minimum", - "maximum" - ] -} diff --git a/test/languages.ts b/test/languages.ts index aac4ef9141..6760981158 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1702,7 +1702,7 @@ export const TypeScriptZodLanguage: Language = { skipJSON: [], skipMiscJSON: false, additionalSchemaFiles: [ - "test/inputs/typescript-zod/unicode-codepoint-length.schema", + "test/inputs/regressions/unicode-codepoint-length.schema", ], skipSchema: [], rendererOptions: {},