Skip to content

Commit

Permalink
feat: Improve nullable (#57)
Browse files Browse the repository at this point in the history
* Add isNotNull util

* Use `nullable()`
  • Loading branch information
fabien0102 committed Nov 22, 2021
1 parent 506e54c commit 0e00f1e
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 9 deletions.
25 changes: 24 additions & 1 deletion src/core/generateZodSchema.test.ts
Expand Up @@ -357,7 +357,7 @@ describe("generateZodSchema", () => {
enemies: z.record(enemySchema),
age: z.number(),
underKryptonite: z.boolean().optional(),
needGlasses: z.union([z.literal(true), z.null()])
needGlasses: z.literal(true).nullable()
});"
`);
});
Expand Down Expand Up @@ -643,6 +643,29 @@ describe("generateZodSchema", () => {
`);
});

it("should deal with nullable", () => {
const source = `export interface A {
/** @minimum 0 */
a: number | null;
/** @minLength 1 */
b: string | null;
/** @pattern ^c$ */
c: string | null;
}
`;

expect(generate(source)).toMatchInlineSnapshot(`
"export const aSchema = z.object({
/** @minimum 0 */
a: z.number().min(0).nullable(),
/** @minLength 1 */
b: z.string().min(1).nullable(),
/** @pattern ^c$ */
c: z.string().regex(/^c$/).nullable()
});"
`);
});

it("should deal with @default with all types", () => {
const source = `export interface WithDefaults {
/**
Expand Down
39 changes: 32 additions & 7 deletions src/core/generateZodSchema.ts
Expand Up @@ -8,6 +8,7 @@ import {
} from "./jsDocTags";
import uniq from "lodash/uniq";
import { findNode } from "../utils/findNode";
import { isNotNull } from "../utils/isNotNull";

const { factory: f } = ts;

Expand Down Expand Up @@ -180,6 +181,7 @@ function buildZodPrimitive({
z,
typeNode,
isOptional,
isNullable,
isPartial,
isRequired,
jsDocTags,
Expand All @@ -190,6 +192,7 @@ function buildZodPrimitive({
z: string;
typeNode: ts.TypeNode;
isOptional: boolean;
isNullable?: boolean;
isPartial?: boolean;
isRequired?: boolean;
jsDocTags: JSDocTags;
Expand All @@ -201,7 +204,8 @@ function buildZodPrimitive({
jsDocTags,
isOptional,
Boolean(isPartial),
Boolean(isRequired)
Boolean(isRequired),
Boolean(isNullable)
);

if (ts.isParenthesizedTypeNode(typeNode)) {
Expand Down Expand Up @@ -395,22 +399,43 @@ function buildZodPrimitive({
}

if (ts.isUnionTypeNode(typeNode)) {
const values = typeNode.types.map((i) =>
const hasNull = Boolean(
typeNode.types.find(
(i) =>
ts.isLiteralTypeNode(i) &&
i.literal.kind === ts.SyntaxKind.NullKeyword
)
);

const nodes = typeNode.types.filter(isNotNull);

// type A = | 'b' is a valid typescript definition
// Zod does not allow `z.union(['b']), so we have to return just the value
if (nodes.length === 1) {
return buildZodPrimitive({
z,
typeNode: nodes[0],
isOptional: false,
isNullable: hasNull,
jsDocTags,
sourceFile,
dependencies,
getDependencyName,
});
}

const values = nodes.map((i) =>
buildZodPrimitive({
z,
typeNode: i,
isOptional: false,
isNullable: false,
jsDocTags: {},
sourceFile,
dependencies,
getDependencyName,
})
);
// type A = | 'b' is a valid typescript definintion
// Zod does not allow `z.union(['b']), so we have to return just the value
if (values.length === 1) {
return values[0];
}
return buildZodSchema(
z,
"union",
Expand Down
9 changes: 8 additions & 1 deletion src/core/jsDocTags.ts
Expand Up @@ -133,12 +133,14 @@ export type ZodProperty = {
* @param isOptional
* @param isPartial
* @param isRequired
* @param isNullable
*/
export function jsDocTagToZodProperties(
jsDocTags: JSDocTags,
isOptional: boolean,
isPartial: boolean,
isRequired: boolean
isRequired: boolean,
isNullable: boolean
) {
const zodProperties: ZodProperty[] = [];
if (jsDocTags.minimum !== undefined) {
Expand Down Expand Up @@ -181,6 +183,11 @@ export function jsDocTagToZodProperties(
identifier: "optional",
});
}
if (isNullable) {
zodProperties.push({
identifier: "nullable",
});
}
if (isPartial) {
zodProperties.push({
identifier: "partial",
Expand Down
14 changes: 14 additions & 0 deletions src/utils/isNotNull.ts
@@ -0,0 +1,14 @@
import ts from "typescript";

/**
* Helper to filter out any `null` node
*
* @param node
* @returns
*/
export function isNotNull(node: ts.TypeNode) {
return (
!ts.isLiteralTypeNode(node) ||
node.literal.kind !== ts.SyntaxKind.NullKeyword
);
}

0 comments on commit 0e00f1e

Please sign in to comment.