From b6874dbcbf226340c725759ed7e354703f4edccd Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:58:51 +0100 Subject: [PATCH 01/11] feat: add combination helper --- src/utils/generateCombinations.test.ts | 62 ++++++++++++++++++++++++++ src/utils/generateCombinations.ts | 28 ++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/utils/generateCombinations.test.ts create mode 100644 src/utils/generateCombinations.ts diff --git a/src/utils/generateCombinations.test.ts b/src/utils/generateCombinations.test.ts new file mode 100644 index 0000000..b69a075 --- /dev/null +++ b/src/utils/generateCombinations.test.ts @@ -0,0 +1,62 @@ +import { generateCombinations } from "./generateCombinations"; + +describe("generateCombinations", () => { + it("should generate all combinations of 1 empty array", () => { + const arrays = [[]]; + const result = generateCombinations(arrays); + expect(result).toEqual([""]); + }); + + it("should generate all combinations of 1 array", () => { + const arrays = [["a", "b"]]; + const result = generateCombinations(arrays); + expect(result).toEqual(["a", "b"]); + }); + + it("should generate all combinations of 2 arrays with one empty array", () => { + const arrays = [["a", "b"], []]; + const result = generateCombinations(arrays); + expect(result).toEqual(["a", "b"]); + }); + + it("should generate all combinations of 2 arrays", () => { + const arrays = [ + ["a", "b"], + ["1", "2"], + ]; + const result = generateCombinations(arrays); + expect(result).toEqual(["a1", "a2", "b1", "b2"]); + }); + + it("should generate all combinations of 3 arrays", () => { + const arrays = [ + ["a", "b"], + ["1", "2"], + ["x", "y"], + ]; + const result = generateCombinations(arrays); + expect(result).toEqual([ + "a1x", + "a1y", + "a2x", + "a2y", + "b1x", + "b1y", + "b2x", + "b2y", + ]); + }); + + it("should generate all combinations of 2 arrays with one array of 1", () => { + const arrays = [["a", "b"], ["1"]]; + const result = generateCombinations(arrays); + expect(result).toEqual(["a1", "b1"]); + }); + + it("should generate all combinations of 4 arrays with 2 arrays of 1", () => { + const arrays = [["a", "b"], ["-"], ["x", "y"], ["$"]]; + const result = generateCombinations(arrays); + + expect(result).toEqual(["a-x$", "a-y$", "b-x$", "b-y$"]); + }); +}); diff --git a/src/utils/generateCombinations.ts b/src/utils/generateCombinations.ts new file mode 100644 index 0000000..e6365b1 --- /dev/null +++ b/src/utils/generateCombinations.ts @@ -0,0 +1,28 @@ +function recursiveGenerateCombinations( + arrays: string[][], + index: number = 0, + current: string[] = [] +): string[][] { + if (index === arrays.length) { + return [current]; + } + + const currentArray = arrays[index].length === 0 ? [""] : arrays[index]; + + const results: string[][] = []; + for (const element of currentArray) { + const combinations = recursiveGenerateCombinations(arrays, index + 1, [ + ...current, + element, + ]); + results.push(...combinations); + } + + return results; +} + +export function generateCombinations(arrays: string[][]): string[] { + return recursiveGenerateCombinations(arrays).map((combination) => + combination.join("") + ); +} From 838f474dfc659f5212023ceacdf9c199b6d03e85 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Mon, 25 Mar 2024 23:19:31 +0100 Subject: [PATCH 02/11] feat: handling Template Literals --- src/core/generateZodSchema.ts | 88 +++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts index be4c1c7..6ce5e60 100644 --- a/src/core/generateZodSchema.ts +++ b/src/core/generateZodSchema.ts @@ -4,6 +4,7 @@ import ts, { factory as f } from "typescript"; import { CustomJSDocFormatTypes } from "../config"; import { findNode } from "../utils/findNode"; import { isNotNull } from "../utils/isNotNull"; +import { generateCombinations } from "../utils/generateCombinations"; import { JSDocTags, ZodProperty, @@ -889,6 +890,93 @@ function buildZodPrimitive({ return buildZodSchema(z, "unknown", [], zodProperties); } + if (ts.isTemplateLiteralTypeNode(typeNode)) { + const spanValues: string[][] = []; + + let hasNull = false; + + spanValues.push([typeNode.head.text]); + + typeNode.templateSpans.forEach((span) => { + if (ts.isTypeReferenceNode(span.type)) { + const targetNode = findNode( + sourceFile, + (n): n is ts.TypeAliasDeclaration => { + return ( + ts.isTypeAliasDeclaration(n) && + ts.isUnionTypeNode(n.type) && + n.name.getText(sourceFile) === + (span.type as ts.TypeReferenceNode).typeName.getText(sourceFile) + ); + } + ); + + if (targetNode && ts.isUnionTypeNode(targetNode.type)) { + hasNull = + hasNull || + Boolean( + targetNode.type.types.find( + (i) => + ts.isLiteralTypeNode(i) && + i.literal.kind === ts.SyntaxKind.NullKeyword + ) + ); + + spanValues.push( + targetNode.type.types + .map((i) => { + if (ts.isLiteralTypeNode(i)) { + if (ts.isStringLiteral(i.literal)) { + return i.literal.text; + } + if (ts.isNumericLiteral(i.literal)) { + return i.literal.text; + } + if (ts.isPrefixUnaryExpression(i.literal)) { + if ( + i.literal.operator === ts.SyntaxKind.MinusToken && + ts.isNumericLiteral(i.literal.operand) + ) { + return "-" + i.literal.operand.text; + } + } + if (i.literal.kind === ts.SyntaxKind.TrueKeyword) { + return "true"; + } + if (i.literal.kind === ts.SyntaxKind.FalseKeyword) { + return "false"; + } + } + return ""; + }) + .filter((i) => i !== "") + ); + } + spanValues.push([span.literal.text]); + } + }); + + // Handling null value outside of the union type + if (hasNull) { + zodProperties.push({ + identifier: "nullable", + }); + } + + return buildZodSchema( + z, + "union", + [ + f.createArrayLiteralExpression( + generateCombinations(spanValues).map((v) => + buildZodSchema(z, "literal", [f.createStringLiteral(v)]) + ) + ), + ], + zodProperties + ); + } + console.warn( ` » Warning: '${ ts.SyntaxKind[typeNode.kind] From 4bf421ca8f3e47eb29d1d21a35acd4305b720469 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:58:37 +0100 Subject: [PATCH 03/11] test: add test for #214 --- src/core/generate.test.ts | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/core/generate.test.ts b/src/core/generate.test.ts index c46043a..6a151ae 100644 --- a/src/core/generate.test.ts +++ b/src/core/generate.test.ts @@ -141,6 +141,94 @@ describe("generate", () => { }); }); + describe("with template literal", () => { + describe("should handle simple reference of one union type", () => { + const sourceText = + 'export type HeroGender = "Man" | "Woman";' + + "export type Heros = `Super${HeroGender}`;"; + + const { getZodSchemasFile, errors } = generate({ + sourceText, + }); + + it("should generate the zod schemas", () => { + expect(getZodSchemasFile("./superhero")).toMatchInlineSnapshot(` + "// Generated by ts-to-zod + import { z } from "zod"; + + export const heroGenderSchema = z.union([z.literal("Man"), z.literal("Woman")]); + + export const herosSchema = z.union([z.literal("SuperMan"), z.literal("SuperWoman")]); + " + `); + }); + + it("should not have any errors", () => { + expect(errors.length).toBe(0); + }); + }); + + describe("should handle combination of 2 Union Types", () => { + const sourceText = + 'export type HeroPrefix = "Super" | "Wonder"' + + 'export type HeroGender = "Man" | "Woman";' + + "export type Heros = `${HeroPrefix}${HeroGender}`;"; + + const { getZodSchemasFile, errors } = generate({ + sourceText, + }); + + it("should generate the zod schemas", () => { + expect(getZodSchemasFile("./superhero")).toMatchInlineSnapshot(` + "// Generated by ts-to-zod + import { z } from "zod"; + + export const heroPrefixSchema = z.union([z.literal("Super"), z.literal("Wonder")]); + + export const heroGenderSchema = z.union([z.literal("Man"), z.literal("Woman")]); + + export const herosSchema = z.union([z.literal("SuperMan"), z.literal("SuperWoman"), z.literal("WonderMan"), z.literal("WonderWoman")]); + " + `); + }); + + it("should not have any errors", () => { + expect(errors.length).toBe(0); + }); + }); + + describe("should handle combination of 2 Union Types with interpolated strings", () => { + const sourceText = + 'export type HeroPrefix = "Super" | "Wonder"' + + 'export type HeroGender = "Man" | "Woman";' + + "export type Heros = `$${HeroPrefix}-${HeroGender}*`;"; + + const { getZodSchemasFile, errors } = generate({ + sourceText, + }); + + it("should generate the zod schemas", () => { + expect(getZodSchemasFile("./superhero")).toMatchInlineSnapshot(` + "// Generated by ts-to-zod + import { z } from "zod"; + + export const heroPrefixSchema = z.union([z.literal("Super"), z.literal("Wonder")]); + + export const heroGenderSchema = z.union([z.literal("Man"), z.literal("Woman")]); + + export const herosSchema = z.union([z.literal("$Super-Man*"), z.literal("$Super-Woman*"), z.literal("$Wonder-Man*"), z.literal("$Wonder-Woman*")]); + " + `); + }); + + it("should not have any errors", () => { + expect(errors.length).toBe(0); + }); + }); + + + }); + describe("with circular references", () => { const sourceText = ` export interface Villain { From 8d1160cd735a4317b8ac6924853b0da51064498f Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:40:48 +0100 Subject: [PATCH 04/11] feat: implementing #64 --- src/core/generate.test.ts | 68 +++++++++++++++++++- src/core/generateZodSchema.ts | 115 +++++++++++++++++++++------------- 2 files changed, 137 insertions(+), 46 deletions(-) diff --git a/src/core/generate.test.ts b/src/core/generate.test.ts index 6a151ae..eda45f5 100644 --- a/src/core/generate.test.ts +++ b/src/core/generate.test.ts @@ -197,6 +197,36 @@ describe("generate", () => { }); }); + describe("should handle combination of 2 Union Types with null option", () => { + const sourceText = + 'export type HeroPrefix = "Super" | "Wonder" | null;' + + 'export type HeroGender = "Man" | "Woman";' + + "export type Heros = `${HeroPrefix}${HeroGender}`;"; + + const { getZodSchemasFile, errors } = generate({ + sourceText, + }); + + it("should generate the zod schemas", () => { + expect(getZodSchemasFile("./superhero")).toMatchInlineSnapshot(` + "// Generated by ts-to-zod + import { z } from "zod"; + + export const heroPrefixSchema = z.union([z.literal("Super"), z.literal("Wonder")]).nullable(); + + export const heroGenderSchema = z.union([z.literal("Man"), z.literal("Woman")]); + + export const herosSchema = z.union([z.literal("SuperMan"), z.literal("SuperWoman"), z.literal("WonderMan"), z.literal("WonderWoman")]).nullable(); + " + `); + }); + + console.log(errors); + it("should not have any errors", () => { + expect(errors.length).toBe(0); + }); + }); + describe("should handle combination of 2 Union Types with interpolated strings", () => { const sourceText = 'export type HeroPrefix = "Super" | "Wonder"' + @@ -226,7 +256,43 @@ describe("generate", () => { }); }); - + describe("should handle enum in string template", () => { + const sourceText = + `export enum Gender { + Man = 'Man', + Woman = 'Woman', + } + + export interface Hero { + name: ` + + "`Super${Gender}`" + + `; + }' + + `; + + const { getZodSchemasFile, errors } = generate({ + sourceText, + }); + + it("should generate the zod schemas", () => { + expect(getZodSchemasFile("./superhero")).toMatchInlineSnapshot(` + "// Generated by ts-to-zod + import { z } from "zod"; + import { Gender } from "./superhero"; + + export const genderSchema = z.nativeEnum(Gender); + + export const heroSchema = z.object({ + name: z.union([z.literal("SuperMan"), z.literal("SuperWoman")]) + }); + " + `); + }); + + it("should not have any errors", () => { + expect(errors.length).toBe(0); + }); + }); }); describe("with circular references", () => { diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts index 6ce5e60..30495a8 100644 --- a/src/core/generateZodSchema.ts +++ b/src/core/generateZodSchema.ts @@ -891,68 +891,65 @@ function buildZodPrimitive({ } if (ts.isTemplateLiteralTypeNode(typeNode)) { - const spanValues: string[][] = []; - + // Handling null outside of the template literal browsing let hasNull = false; + // Extracting the values from the template literal + const spanValues: string[][] = []; spanValues.push([typeNode.head.text]); typeNode.templateSpans.forEach((span) => { if (ts.isTypeReferenceNode(span.type)) { const targetNode = findNode( sourceFile, - (n): n is ts.TypeAliasDeclaration => { + (n): n is ts.TypeAliasDeclaration | ts.EnumDeclaration => { return ( - ts.isTypeAliasDeclaration(n) && - ts.isUnionTypeNode(n.type) && + ((ts.isTypeAliasDeclaration(n) && ts.isUnionTypeNode(n.type)) || + ts.isEnumDeclaration(n)) && n.name.getText(sourceFile) === (span.type as ts.TypeReferenceNode).typeName.getText(sourceFile) ); } ); - if (targetNode && ts.isUnionTypeNode(targetNode.type)) { - hasNull = - hasNull || - Boolean( - targetNode.type.types.find( - (i) => - ts.isLiteralTypeNode(i) && - i.literal.kind === ts.SyntaxKind.NullKeyword - ) - ); + if (targetNode) { + if ( + ts.isTypeAliasDeclaration(targetNode) && + ts.isUnionTypeNode(targetNode.type) + ) { + hasNull = + hasNull || + Boolean( + targetNode.type.types.find( + (i) => + ts.isLiteralTypeNode(i) && + i.literal.kind === ts.SyntaxKind.NullKeyword + ) + ); - spanValues.push( - targetNode.type.types - .map((i) => { - if (ts.isLiteralTypeNode(i)) { - if (ts.isStringLiteral(i.literal)) { - return i.literal.text; - } - if (ts.isNumericLiteral(i.literal)) { - return i.literal.text; - } - if (ts.isPrefixUnaryExpression(i.literal)) { - if ( - i.literal.operator === ts.SyntaxKind.MinusToken && - ts.isNumericLiteral(i.literal.operand) - ) { - return "-" + i.literal.operand.text; - } - } - if (i.literal.kind === ts.SyntaxKind.TrueKeyword) { - return "true"; - } - if (i.literal.kind === ts.SyntaxKind.FalseKeyword) { - return "false"; - } - } - return ""; - }) - .filter((i) => i !== "") - ); + spanValues.push( + targetNode.type.types + .map((i) => { + if (ts.isLiteralTypeNode(i)) + return extractLiteralValue(i.literal); + return ""; + }) + .filter((i) => i !== "") + ); + } else if (ts.isEnumDeclaration(targetNode)) { + spanValues.push( + targetNode.members + // .filter((i) => ) + .map((i) => { + if (ts.isEnumMember(i) && i.initializer) + return extractLiteralValue(i.initializer); + return ""; + }) + .filter((i) => i !== "") + ); + } + spanValues.push([span.literal.text]); } - spanValues.push([span.literal.text]); } }); @@ -1319,3 +1316,31 @@ function buildSchemaReference( throw new Error("Unknown IndexedAccessTypeNode.objectType type"); } + +/** + * + */ + +function extractLiteralValue(node: ts.Expression): string { + if (ts.isStringLiteral(node)) { + return node.text; + } + if (ts.isNumericLiteral(node)) { + return node.text; + } + if (ts.isPrefixUnaryExpression(node)) { + if ( + node.operator === ts.SyntaxKind.MinusToken && + ts.isNumericLiteral(node.operand) + ) { + return "-" + node.operand.text; + } + } + if (node.kind === ts.SyntaxKind.TrueKeyword) { + return "true"; + } + if (node.kind === ts.SyntaxKind.FalseKeyword) { + return "false"; + } + return ""; +} From b577cbd8e1232e5a1daf484a9c4640568bdcffc7 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:42:51 +0100 Subject: [PATCH 05/11] test: adding template literal as example --- example/heros.ts | 1 + example/heros.zod.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/example/heros.ts b/example/heros.ts index 887a83c..fd78c43 100644 --- a/example/heros.ts +++ b/example/heros.ts @@ -17,6 +17,7 @@ export interface Enemy extends Person { name: string; powers: EnemyPower[]; inPrison: boolean; + mainPower: `${EnemyPower}`; } export type SupermanEnemy = Superman["enemies"][-1]; diff --git a/example/heros.zod.ts b/example/heros.zod.ts index 7495ee2..46aa9f6 100644 --- a/example/heros.zod.ts +++ b/example/heros.zod.ts @@ -14,6 +14,11 @@ export const enemySchema = personSchema.extend({ name: z.string(), powers: z.array(enemyPowerSchema), inPrison: z.boolean(), + mainPower: z.union([ + z.literal("flight"), + z.literal("strength"), + z.literal("speed"), + ]), }); export const supermanSchema = z.object({ From 49c25f914dd96cac3c737c3cb00ffa994760fd87 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:18:14 +0100 Subject: [PATCH 06/11] fix: adding fallback for unsupported template literal cases --- src/core/generateZodSchema.ts | 53 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts index 30495a8..7e0c338 100644 --- a/src/core/generateZodSchema.ts +++ b/src/core/generateZodSchema.ts @@ -891,6 +891,8 @@ function buildZodPrimitive({ } if (ts.isTemplateLiteralTypeNode(typeNode)) { + let ignoreNode = false; + // Handling null outside of the template literal browsing let hasNull = false; @@ -939,17 +941,30 @@ function buildZodPrimitive({ } else if (ts.isEnumDeclaration(targetNode)) { spanValues.push( targetNode.members - // .filter((i) => ) .map((i) => { - if (ts.isEnumMember(i) && i.initializer) - return extractLiteralValue(i.initializer); + if (i.initializer) return extractLiteralValue(i.initializer); + else { + console.warn( + ` » Warning: enum member without initializer '${targetNode.name.getText( + sourceFile + )}.${i.name.getText(sourceFile)}' is not supported.` + ); + ignoreNode = true; + } return ""; }) .filter((i) => i !== "") ); } - spanValues.push([span.literal.text]); + } else { + console.warn( + ` » Warning: reference not found '${span.type.getText( + sourceFile + )}' in Template Literal.` + ); + ignoreNode = true; } + spanValues.push([span.literal.text]); } }); @@ -959,19 +974,23 @@ function buildZodPrimitive({ identifier: "nullable", }); } - - return buildZodSchema( - z, - "union", - [ - f.createArrayLiteralExpression( - generateCombinations(spanValues).map((v) => - buildZodSchema(z, "literal", [f.createStringLiteral(v)]) - ) - ), - ], - zodProperties - ); + if (!ignoreNode) { + return buildZodSchema( + z, + "union", + [ + f.createArrayLiteralExpression( + generateCombinations(spanValues).map((v) => + buildZodSchema(z, "literal", [f.createStringLiteral(v)]) + ) + ), + ], + zodProperties + ); + } else { + console.warn(` » ...falling back into 'z.any()'`); + return buildZodSchema(z, "any", [], zodProperties); + } } console.warn( From a6eb8f7ffd875bea8ae1dd5688278a6064b27590 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:39:28 +0100 Subject: [PATCH 07/11] refacto: extract extractLiteralValue --- src/core/generateZodSchema.ts | 29 +---------------------- src/utils/extractLiteralValue.test.ts | 34 +++++++++++++++++++++++++++ src/utils/extractLiteralValue.ts | 28 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 src/utils/extractLiteralValue.test.ts create mode 100644 src/utils/extractLiteralValue.ts diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts index 7e0c338..1ab654d 100644 --- a/src/core/generateZodSchema.ts +++ b/src/core/generateZodSchema.ts @@ -11,6 +11,7 @@ import { getJSDocTags, jsDocTagToZodProperties, } from "./jsDocTags"; +import { extractLiteralValue } from "../utils/extractLiteralValue"; export interface GenerateZodSchemaProps { /** @@ -1335,31 +1336,3 @@ function buildSchemaReference( throw new Error("Unknown IndexedAccessTypeNode.objectType type"); } - -/** - * - */ - -function extractLiteralValue(node: ts.Expression): string { - if (ts.isStringLiteral(node)) { - return node.text; - } - if (ts.isNumericLiteral(node)) { - return node.text; - } - if (ts.isPrefixUnaryExpression(node)) { - if ( - node.operator === ts.SyntaxKind.MinusToken && - ts.isNumericLiteral(node.operand) - ) { - return "-" + node.operand.text; - } - } - if (node.kind === ts.SyntaxKind.TrueKeyword) { - return "true"; - } - if (node.kind === ts.SyntaxKind.FalseKeyword) { - return "false"; - } - return ""; -} diff --git a/src/utils/extractLiteralValue.test.ts b/src/utils/extractLiteralValue.test.ts new file mode 100644 index 0000000..1d6e1ce --- /dev/null +++ b/src/utils/extractLiteralValue.test.ts @@ -0,0 +1,34 @@ +import { factory } from "typescript"; +import { extractLiteralValue } from "./extractLiteralValue"; + +describe("extractLiteralValue", () => { + it("should extract string literal value", () => { + const source = factory.createStringLiteral("hello"); + expect(extractLiteralValue(source)).toBe("hello"); + }); + + it("should extract numeric literal value", () => { + const source = factory.createNumericLiteral("42"); + expect(extractLiteralValue(source)).toBe("42"); + }); + + it("should extract negative numeric literal value", () => { + const source = factory.createNumericLiteral("-42"); + expect(extractLiteralValue(source)).toBe("-42"); + }); + + it("should extract true literal value", () => { + const source = factory.createTrue(); + expect(extractLiteralValue(source)).toBe("true"); + }); + + it("should extract false literal value", () => { + const source = factory.createFalse(); + expect(extractLiteralValue(source)).toBe("false"); + }); + + it("should return empty string for unknown literal value", () => { + const source = factory.createNull(); + expect(extractLiteralValue(source)).toBe(""); + }); +}); diff --git a/src/utils/extractLiteralValue.ts b/src/utils/extractLiteralValue.ts new file mode 100644 index 0000000..a2a66f2 --- /dev/null +++ b/src/utils/extractLiteralValue.ts @@ -0,0 +1,28 @@ +import ts from "typescript"; + +/** + * Extract the string representation of a literal value + */ +export function extractLiteralValue(node: ts.Expression): string { + if (ts.isStringLiteral(node)) { + return node.text; + } + if (ts.isNumericLiteral(node)) { + return node.text; + } + if (ts.isPrefixUnaryExpression(node)) { + if ( + node.operator === ts.SyntaxKind.MinusToken && + ts.isNumericLiteral(node.operand) + ) { + return "-" + node.operand.text; + } + } + if (node.kind === ts.SyntaxKind.TrueKeyword) { + return "true"; + } + if (node.kind === ts.SyntaxKind.FalseKeyword) { + return "false"; + } + return ""; +} From 82c03438f9a2beb844df846de7c9beb76642dbdc Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:40:18 +0100 Subject: [PATCH 08/11] doc: adding warning for unsupported case --- src/core/generateZodSchema.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts index 1ab654d..e996e68 100644 --- a/src/core/generateZodSchema.ts +++ b/src/core/generateZodSchema.ts @@ -966,6 +966,13 @@ function buildZodPrimitive({ ignoreNode = true; } spanValues.push([span.literal.text]); + } else { + console.warn( + ` » Warning: node '${span.type.getText( + sourceFile + )}' not supported in Template Literal.` + ); + ignoreNode = true; } }); @@ -975,6 +982,7 @@ function buildZodPrimitive({ identifier: "nullable", }); } + if (!ignoreNode) { return buildZodSchema( z, From 1f6dc995564b3d01064575d00f5ad526b15f1682 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:45:33 +0100 Subject: [PATCH 09/11] refacto: import ordering --- src/core/generateZodSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts index e996e68..039d3e7 100644 --- a/src/core/generateZodSchema.ts +++ b/src/core/generateZodSchema.ts @@ -5,13 +5,13 @@ import { CustomJSDocFormatTypes } from "../config"; import { findNode } from "../utils/findNode"; import { isNotNull } from "../utils/isNotNull"; import { generateCombinations } from "../utils/generateCombinations"; +import { extractLiteralValue } from "../utils/extractLiteralValue"; import { JSDocTags, ZodProperty, getJSDocTags, jsDocTagToZodProperties, } from "./jsDocTags"; -import { extractLiteralValue } from "../utils/extractLiteralValue"; export interface GenerateZodSchemaProps { /** From fe9da35a674f1bf2c2b538fc9cf50295f1be8815 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:59:38 +0100 Subject: [PATCH 10/11] test: fix use case --- src/utils/extractLiteralValue.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/utils/extractLiteralValue.test.ts b/src/utils/extractLiteralValue.test.ts index 1d6e1ce..1f7a740 100644 --- a/src/utils/extractLiteralValue.test.ts +++ b/src/utils/extractLiteralValue.test.ts @@ -1,4 +1,4 @@ -import { factory } from "typescript"; +import ts, { factory } from "typescript"; import { extractLiteralValue } from "./extractLiteralValue"; describe("extractLiteralValue", () => { @@ -13,7 +13,10 @@ describe("extractLiteralValue", () => { }); it("should extract negative numeric literal value", () => { - const source = factory.createNumericLiteral("-42"); + const source = factory.createPrefixUnaryExpression( + ts.SyntaxKind.MinusToken, + factory.createNumericLiteral("42") + ); expect(extractLiteralValue(source)).toBe("-42"); }); From 1ecadc73f5ac3550b2498329d9c2f21765ce0dd0 Mon Sep 17 00:00:00 2001 From: Thomas V <2619415+tvillaren@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:53:53 +0100 Subject: [PATCH 11/11] fix: more logical behaviour --- src/utils/generateCombinations.test.ts | 2 +- src/utils/generateCombinations.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/generateCombinations.test.ts b/src/utils/generateCombinations.test.ts index b69a075..5d00139 100644 --- a/src/utils/generateCombinations.test.ts +++ b/src/utils/generateCombinations.test.ts @@ -4,7 +4,7 @@ describe("generateCombinations", () => { it("should generate all combinations of 1 empty array", () => { const arrays = [[]]; const result = generateCombinations(arrays); - expect(result).toEqual([""]); + expect(result).toEqual([]); }); it("should generate all combinations of 1 array", () => { diff --git a/src/utils/generateCombinations.ts b/src/utils/generateCombinations.ts index e6365b1..a0f51e0 100644 --- a/src/utils/generateCombinations.ts +++ b/src/utils/generateCombinations.ts @@ -22,7 +22,7 @@ function recursiveGenerateCombinations( } export function generateCombinations(arrays: string[][]): string[] { - return recursiveGenerateCombinations(arrays).map((combination) => - combination.join("") - ); + return recursiveGenerateCombinations(arrays) + .map((combination) => combination.join("")) + .filter((combination) => combination !== ""); }