Skip to content

Commit

Permalink
feat: Support IndexAccessType (#51)
Browse files Browse the repository at this point in the history
* Deal with indexAccessType nodes

* Deal with some advanced cases

* Update zod

* Use zod element/valueSchema

* Add jsDoc parser in example
  • Loading branch information
fabien0102 committed Oct 25, 2021
1 parent 47616ec commit 2b28266
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 8 deletions.
44 changes: 44 additions & 0 deletions example/heros.ts
Expand Up @@ -17,11 +17,16 @@ export interface Enemy {
inPrison: boolean;
}

export type SupermanEnemy = Superman["enemies"][-1];
export type SupermanName = Superman["name"];
export type SupermanInvinciblePower = Superman["powers"][2];

export interface Superman {
name: "superman" | "clark kent" | "kal-l";
enemies: Record<string, Enemy>;
age: number;
underKryptonite?: boolean;
powers: ["fly", "laser", "invincible"];
}

export interface Villain {
Expand Down Expand Up @@ -89,3 +94,42 @@ export type GetSupermanSkill = (
skillName: string,
withKryptonite?: boolean
) => string;

export interface HeroContact {
/**
* The email of the hero.
*
* @format email
*/
email: string;

/**
* The name of the hero.
*
* @minLength 2
* @maxLength 50
*/
name: string;

/**
* The phone number of the hero.
*
* @pattern ^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$
*/
phoneNumber: string;

/**
* Does the hero has super power?
*
* @default true
*/
hasSuperPower?: boolean;

/**
* The age of the hero
*
* @minimum 0
* @maximum 500
*/
age: number;
}
20 changes: 20 additions & 0 deletions example/heros.zod.ts
Expand Up @@ -23,6 +23,11 @@ export const supermanSchema = z.object({
enemies: z.record(enemySchema),
age: z.number(),
underKryptonite: z.boolean().optional(),
powers: z.tuple([
z.literal("fly"),
z.literal("laser"),
z.literal("invincible"),
]),
});

export const villainSchema: z.ZodSchema<Villain> = z.lazy(() =>
Expand Down Expand Up @@ -69,3 +74,18 @@ export const getSupermanSkillSchema = z
.function()
.args(z.string(), z.boolean().optional())
.returns(z.string());

export const heroContactSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(50),
phoneNumber: z.string().regex(/^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$/),
hasSuperPower: z.boolean().optional().default(true),
age: z.number().min(0).max(500),
});

export const supermanEnemySchema = supermanSchema.shape.enemies.valueSchema;

export const supermanNameSchema = supermanSchema.shape.name;

export const supermanInvinciblePowerSchema =
supermanSchema.shape.powers.items[2];
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -56,7 +56,7 @@
"tslib": "^2.1.0",
"tsutils": "^3.21.0",
"typescript": "4.2.3",
"zod": "^3.5.1"
"zod": "^3.9.8"
},
"devDependencies": {
"@types/async": "^3.2.6",
Expand Down
125 changes: 125 additions & 0 deletions src/core/generateZodSchema.test.ts
Expand Up @@ -384,6 +384,131 @@ describe("generateZodSchema", () => {
`);
});

it("should deal with index access type (1st level)", () => {
const source = `export type SupermanName = Superman["name"]`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanNameSchema = supermanSchema.shape.name;"`
);
});

it("should deal with index access type (nested level)", () => {
const source = `export type SupermanFlyPower = Superman["power"]["fly"]`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanFlyPowerSchema = supermanSchema.shape.power.shape.fly;"`
);
});

it("should deal with index access type (array item)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Array<Power>
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.element;"`
);
});

it("should deal with index access type (array item bis)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Power[]
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.element;"`
);
});

it("should deal with index access type (record item)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Record<string, Power>
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.valueSchema;"`
);
});

it("should deal with index access type (record item) (interface)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export interface Superman {
powers: Record<string, Power>
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.valueSchema;"`
);
});

it("should deal with index access type (tuple)", () => {
const source = `export type SupermanPower = Superman["powers"][1];
export type Superman = {
powers: ["fly", "burnStuff"]
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.items[1];"`
);
});

// TODO
it.skip("should deal with index access type (nested array item)", () => {
const source = `export type SupermanPower = Superman["powers"][-1][-1];
export type Superman = {
powers: Power[][]
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.element.element;"`
);
});

it("should deal with index access type (inline array item)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Array<{type: string}>
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.element;"`
);
});

it("should deal with index access type (inline array item bis)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: {type: string}[]
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.element;"`
);
});

it("should deal with index access type (inline record)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Record<string, {type: string}>
};`;

expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanPowerSchema = supermanSchema.shape.powers.valueSchema;"`
);
});

it("should deal with parenthesized type", () => {
const source = `export type SecretVillain = (NormalGuy | Villain);`;
expect(generate(source)).toMatchInlineSnapshot(
Expand Down

0 comments on commit 2b28266

Please sign in to comment.