From b6f98dc4164dbc80a261ea72e8ae77e79509f1bc Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 2 Jun 2026 13:15:05 -0400 Subject: [PATCH 1/2] fix(zod): escape newlines in $ref description with generateReusableSchemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `generateReusableSchemas: true` and an object property is a $ref with a multi-line `description` sibling, the description was emitted as `Name.describe("…raw text…")` via `escape()`, which only escapes quote chars. Multi-line descriptions produced literal newlines inside a double-quoted string literal, generating invalid TypeScript (TS1002 unterminated string literal). Use the same single-quoted, fully JS-escaped form as the primitive description path (`jsStringEscape`), so `$ref` siblings and primitive fields are escaped identically. Fixes #3517 Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/zod/src/index.ts | 9 ++++++++- packages/zod/src/zod.test.ts | 25 ++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/zod/src/index.ts b/packages/zod/src/index.ts index a7c4865db..de90cace0 100644 --- a/packages/zod/src/index.ts +++ b/packages/zod/src/index.ts @@ -281,7 +281,14 @@ export const generateZodValidationSchemaDefinition = ( } if (typeof siblingSchema.description === 'string') { - functions.push(['describe', `"${escape(siblingSchema.description)}"`]); + // Use the same single-quoted, fully JS-escaped form as the primitive + // description path (see `pushDescriptionOrMeta`). `escape` only escapes + // quote chars, so a multi-line description would emit raw newlines and + // break the generated string literal (TS1002). + functions.push([ + 'describe', + `'${jsStringEscape(siblingSchema.description)}'`, + ]); } }; diff --git a/packages/zod/src/zod.test.ts b/packages/zod/src/zod.test.ts index f43ab0ffb..88d52b9a9 100644 --- a/packages/zod/src/zod.test.ts +++ b/packages/zod/src/zod.test.ts @@ -4078,7 +4078,30 @@ describe('generateZodValidationSchemaDefinition`', () => { ); expect(result.functions).toEqual([ ['namedRef', { name: 'Pet', sourceRef: '#/components/schemas/Pet' }], - ['describe', '"optional pet"'], + ['describe', "'optional pet'"], + ]); + }); + + it('escapes newlines in a multi-line description sibling on a $ref (#3517)', () => { + const context = makeContextSpec({ + spec: { components: { schemas: { Pet: { type: 'object' } } } }, + }); + const result = generateZodValidationSchemaDefinition( + { + $ref: '#/components/schemas/Pet', + description: 'Subject identifier.\n\nMust be normalized first.', + } as never, + context, + 'someField', + false, + false, + { required: true, useReusableSchemas: true }, + ); + // Same single-quoted, \n-escaped form as a primitive sibling — no raw + // newlines that would break the generated string literal (TS1002). + expect(result.functions).toEqual([ + ['namedRef', { name: 'Pet', sourceRef: '#/components/schemas/Pet' }], + ['describe', "'Subject identifier.\\n\\nMust be normalized first.'"], ]); }); From 696b4b34ffd8da3e32313213eb8e3d1b5ed59777 Mon Sep 17 00:00:00 2001 From: Evgeny Zaytsev Date: Wed, 3 Jun 2026 15:03:20 -0400 Subject: [PATCH 2/2] fix: lint --- packages/zod/src/zod.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/zod/src/zod.test.ts b/packages/zod/src/zod.test.ts index 88d52b9a9..06e23b27a 100644 --- a/packages/zod/src/zod.test.ts +++ b/packages/zod/src/zod.test.ts @@ -4101,7 +4101,10 @@ describe('generateZodValidationSchemaDefinition`', () => { // newlines that would break the generated string literal (TS1002). expect(result.functions).toEqual([ ['namedRef', { name: 'Pet', sourceRef: '#/components/schemas/Pet' }], - ['describe', "'Subject identifier.\\n\\nMust be normalized first.'"], + [ + 'describe', + String.raw`'Subject identifier.\n\nMust be normalized first.'`, + ], ]); });