diff --git a/.agents/skills/enable-small-tests/SKILL.md b/.agents/skills/enable-small-tests/SKILL.md index 294e0b629d..7fba0c25fc 100644 --- a/.agents/skills/enable-small-tests/SKILL.md +++ b/.agents/skills/enable-small-tests/SKILL.md @@ -17,7 +17,7 @@ Turn disabled coverage into small, independently reviewable fixes. Read the repo - **Use shared test cases, not new per-bug fixtures.** General inputs, especially keyword cases, must run across languages. A newly discovered bug does not by itself justify a new driver, fixture registration, schema, or language-only input list. - Preserve intended generated naming. Reject “stabilize names” changes whose only purpose is making JSON-versus-schema output text identical. Renaming is justified when the original name actually prevents compilation or runtime behavior, with evidence. - Modify only relevant code; respect existing abstraction boundaries. No unrelated cleanup, version bumps, or tombstone comments/docs explaining removed behavior. -- Emit new generated functions, definitions, and imports only when the input types or renderer options require them. Validate generated output both with and without that requirement. +- Emit new generated functions, definitions, and imports only when the input types or renderer options require them. Review the generated-output diff for unnecessary helpers, codecs, imports, or renamed types; do not add unit tests solely to assert helper or import presence or absence, or codec definition counts. Keep unit tests for API behavior and meaningful public naming regressions that fixtures cannot express. - Write descriptions, comments, commits, and summaries tersely. Explain **what and why**, include useful links and validation, and omit praise and conversational history. ## Find candidates diff --git a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts index 140cbe28da..f10abc01e8 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/TypeScriptZodRenderer.ts @@ -200,8 +200,14 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { return ["z.union([", ...arrayIntercalate(", ", children), "])"]; }, (_transformedStringType) => { + if (_transformedStringType.kind === "date") { + return "dateSchema"; + } + if (_transformedStringType.kind === "time") { + return "timeSchema"; + } if (_transformedStringType.kind === "date-time") { - return "z.string().pipe(z.coerce.date())"; + return "dateTimeSchema"; } if (_transformedStringType.kind === "uuid") { return "z.string().uuid()"; @@ -459,6 +465,22 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { protected emitSchemas(): void { this.ensureBlankLine(); + const kinds = this.forEachType((type) => type.kind); + if (kinds.has("date")) { + this.emitLine( + 'const dateSchema = z.string().regex(/^\\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2]\\d|3[01])$/).refine(value => (new Date(value + "T00:00:00Z").toJSON() || "").slice(0, 10) === value);', + ); + } + if (kinds.has("time")) { + this.emitLine( + "const timeSchema = z.string().regex(/^(?:[01]\\d|2[0-3]):[0-5]\\d:(?:[0-5]\\d|60)(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$/i);", + ); + } + if (kinds.has("date-time")) { + this.emitLine( + 'const dateTimeSchema = z.string().refine(value => z.string().datetime({ offset: true }).safeParse(value.toUpperCase()).success && (new Date(value.slice(0, 10) + "T00:00:00Z").toJSON() || "").slice(0, 10) === value.slice(0, 10)).pipe(z.coerce.date());', + ); + } this.forEachEnum( "leading-and-interposing", diff --git a/packages/quicktype-core/src/language/TypeScriptZod/language.ts b/packages/quicktype-core/src/language/TypeScriptZod/language.ts index 77392f56f8..67793bb3e2 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod/language.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod/language.ts @@ -43,6 +43,8 @@ export class TypeScriptZodTargetLanguage extends TargetLanguage< const mapping: Map = new Map(); const dateTimeType = "date-time"; + mapping.set("date", "date"); + mapping.set("time", "time"); mapping.set("date-time", dateTimeType); mapping.set("uuid", "uuid"); mapping.set("bool-string", "bool-string"); diff --git a/test/inputs/schema/date-time.5.fail.date.json b/test/inputs/schema/date-time.5.fail.date.json new file mode 100644 index 0000000000..ffc8bfd914 --- /dev/null +++ b/test/inputs/schema/date-time.5.fail.date.json @@ -0,0 +1,7 @@ +{ + "date": "2023-02-29", + "time": "23:20:50.52Z", + "date-time": "2018-08-13T21:31:01+01:00", + "union-array": ["1985-04-12", "23:20:50.52Z"], + "complex-union-array": ["2018-08-13T21:31:01+00:10", "foo", 123] +} diff --git a/test/inputs/schema/date-time.6.fail.time.json b/test/inputs/schema/date-time.6.fail.time.json new file mode 100644 index 0000000000..6699c6c63e --- /dev/null +++ b/test/inputs/schema/date-time.6.fail.time.json @@ -0,0 +1,7 @@ +{ + "date": "1985-04-12", + "time": "29:99:99Z", + "date-time": "2018-08-13T21:31:01+01:00", + "union-array": ["1985-04-12", "23:20:50.52Z"], + "complex-union-array": ["2018-08-13T21:31:01+00:10", "foo", 123] +} diff --git a/test/languages.ts b/test/languages.ts index 8f534c31d2..ebfdb206ec 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1743,6 +1743,8 @@ export const TypeScriptZodLanguage: Language = { "union", "no-defaults", "date-time", + "date", + "time", "uuid", "bool-string", "integer-string",