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..06e23b27a 100644 --- a/packages/zod/src/zod.test.ts +++ b/packages/zod/src/zod.test.ts @@ -4078,7 +4078,33 @@ 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', + String.raw`'Subject identifier.\n\nMust be normalized first.'`, + ], ]); });