Repository: fabien0102/ts-to-zod (ts)

Issue:
Nullable with Parenthesis is Ignored
## Bug description

Nullable parenthesis is not working

### Input

```ts
export interface Root {
  id: number;
}

export interface NameProps {
  name: string;
}

export interface NumProps {
  num: number;
}

export type Dem = (Root & NameProps & NumProps) | null;

export type Dom = ("a" | "b") | null;

```

### Expected output

```ts
import { z } from "zod";

export const rootSchema = z.object({
  id: z.number(),
});

export const namePropsSchema = z.object({
  name: z.string(),
});

export const numPropsSchema = z.object({
  num: z.number(),
});

export const demSchema = rootSchema
  .and(namePropsSchema)
  .and(numPropsSchema)
  .nullable();

export const domSchema = z.union([z.literal("a"), z.literal("b")]).nullable();


```

### Actual output

```ts
import { z } from "zod";

export const rootSchema = z.object({
  id: z.number(),
});

export const namePropsSchema = z.object({
  name: z.string(),
});

export const numPropsSchema = z.object({
  num: z.number(),
});

export const demSchema = rootSchema.and(namePropsSchema).and(numPropsSchema);

export const domSchema = z.union([z.literal("a"), z.literal("b")]);

```

**Versions**

- Typescript: `v5.2.2`
- Zod: `v3.6.0`


Pull request:
Parenthesized or null
# Why
Parenthesized or null is a valid TS types, but the current generator does not recognized this.

<!-- Why did you make this PR? What problem does it solve? -->
Fix #197 

Patch:
diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts
index 80645a1..440ee66 100644
--- a/src/core/generateZodSchema.ts
+++ b/src/core/generateZodSchema.ts
@@ -269,6 +269,7 @@ function buildZodPrimitive({
     return buildZodPrimitive({
       z,
       typeNode: typeNode.type,
+      isNullable,
       isOptional,
       jsDocTags,
       customJSDocFormatTypes,
@@ -745,7 +746,7 @@ function buildZodPrimitive({
       customJSDocFormatTypes,
     });
 
-    return rest.reduce(
+    const zodCall = rest.reduce(
       (intersectionSchema, node) =>
         f.createCallExpression(
           f.createPropertyAccessExpression(
@@ -769,6 +770,8 @@ function buildZodPrimitive({
         ),
       basePrimitive
     );
+
+    return withZodProperties(zodCall, zodProperties);
   }
 
   if (ts.isLiteralTypeNode(typeNode)) {


Test patch:
diff --git a/src/core/generateZodSchema.test.ts b/src/core/generateZodSchema.test.ts
index 512ee53..bc4f96f 100644
--- a/src/core/generateZodSchema.test.ts
+++ b/src/core/generateZodSchema.test.ts
@@ -605,13 +605,34 @@ describe("generateZodSchema", () => {
     );
   });
 
-  it("should deal with parenthesized type", () => {
+  it("should deal with parenthesized schema type", () => {
     const source = `export type SecretVillain = (NormalGuy | Villain);`;
     expect(generate(source)).toMatchInlineSnapshot(
       `"export const secretVillainSchema = z.union([normalGuySchema, villainSchema]);"`
     );
   });
 
+  it("should deal with parenthesized type or null", () => {
+    const source = `export type SecretVillain = (NormalGuy | Villain) | null;`;
+    expect(generate(source)).toMatchInlineSnapshot(
+      `"export const secretVillainSchema = z.union([normalGuySchema, villainSchema]).nullable();"`
+    );
+  });
+
+  it("should deal with literal parenthesized type or null", () => {
+    const source = `export type Example = ("A" | "B") | null;`;
+    expect(generate(source)).toMatchInlineSnapshot(
+      `"export const exampleSchema = z.union([z.literal("A"), z.literal("B")]).nullable();"`
+    );
+  });
+
+  it("should deal with joined schema parenthesized type or null", () => {
+    const source = `export type person = (NormalGuy & BadGuy & randomGuy) | null;`;
+    expect(generate(source)).toMatchInlineSnapshot(
+      `"export const personSchema = normalGuySchema.and(badGuySchema).and(randomGuySchema).nullable();"`
+    );
+  });
+
   it("should deal with index signature", () => {
     const source = `export type Movies = {[title: string]: Movie};`;
     expect(generate(source)).toMatchInlineSnapshot(