Skip to content

Commit

Permalink
fix: empty interfaces' extends statements are ignored #108 (#109)
Browse files Browse the repository at this point in the history
* fix: empty interfaces' extends statements are ignored #108

* style: trim trailing whitespace

* test: add a test case for empty interfaces' extends statements
  • Loading branch information
NexZhu committed Jan 30, 2023
1 parent 5741953 commit 4ad2d09
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/cli.ts
Expand Up @@ -43,7 +43,7 @@ try {
oclifError(
`"${tsToZodConfigJs}" invalid:
${e.message}
Please fix the invalid configuration
You can generate a new config with --init`,
{ exit: false }
Expand Down
14 changes: 7 additions & 7 deletions src/core/generate.test.ts
Expand Up @@ -17,7 +17,7 @@ describe("generate", () => {
**/
email: string;
}
const fly = () => console.log("I can fly!");
`;

Expand Down Expand Up @@ -79,7 +79,7 @@ describe("generate", () => {

describe("with enums", () => {
const sourceText = `
export enum Superhero {
export enum Superhero {
Superman = "superman"
ClarkKent = "clark-kent"
};
Expand Down Expand Up @@ -145,7 +145,7 @@ describe("generate", () => {
powers: string[];
friends: Villain[];
}
export interface EvilPlan {
owner: Villain;
description: string;
Expand Down Expand Up @@ -291,7 +291,7 @@ describe("generate", () => {
/**
* Only this interface should be generated
*
*
* @zod
*/
export interface Superman {
Expand Down Expand Up @@ -379,10 +379,10 @@ describe("generate", () => {
const sourceText = `
export namespace Metropolis {
export type Name = "superman" | "clark kent" | "kal-l";
// Note that the Superman is declared after
export type BadassSuperman = Omit<Superman, "underKryptonite">;
export interface Superman {
name: Name;
age: number;
Expand All @@ -392,7 +392,7 @@ describe("generate", () => {
**/
email: string;
}
const fly = () => console.log("I can fly!");
}
`;
Expand Down
45 changes: 26 additions & 19 deletions src/core/generateZodSchema.test.ts
Expand Up @@ -109,7 +109,7 @@ describe("generateZodSchema", () => {
});

it("should generate a nativeEnum schema", () => {
const source = `export enum Superhero = {
const source = `export enum Superhero = {
Superman = "superman",
ClarkKent = "clark_kent",
};`;
Expand Down Expand Up @@ -384,6 +384,13 @@ describe("generateZodSchema", () => {
`);
});

it("should generate an variable assignment if an extending type has no new fields", () => {
const source = "export interface Superman extends Clark {}";
expect(generate(source)).toMatchInlineSnapshot(
`"export const supermanSchema = clarkSchema;"`
);
});

it("should generate a merged schema when two extends are used", () => {
const source = `export interface Superman extends Clark extends KalL {
withPower: boolean;
Expand Down Expand Up @@ -449,7 +456,7 @@ describe("generateZodSchema", () => {

it("should deal with index access type (array item)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Array<Power>
};`;
Expand All @@ -461,7 +468,7 @@ describe("generateZodSchema", () => {

it("should deal with index access type (array item bis)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Power[]
};`;
Expand All @@ -473,7 +480,7 @@ describe("generateZodSchema", () => {

it("should deal with index access type (record item)", () => {
const source = `export type SupermanPower = Superman["powers"][-1];
export type Superman = {
powers: Record<string, Power>
};`;
Expand All @@ -485,7 +492,7 @@ describe("generateZodSchema", () => {

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>
};`;
Expand All @@ -497,7 +504,7 @@ describe("generateZodSchema", () => {

it("should deal with index access type (tuple)", () => {
const source = `export type SupermanPower = Superman["powers"][1];
export type Superman = {
powers: ["fly", "burnStuff"]
};`;
Expand All @@ -510,7 +517,7 @@ describe("generateZodSchema", () => {
// 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[][]
};`;
Expand All @@ -522,7 +529,7 @@ describe("generateZodSchema", () => {

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}>
};`;
Expand All @@ -534,7 +541,7 @@ describe("generateZodSchema", () => {

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}[]
};`;
Expand All @@ -546,7 +553,7 @@ describe("generateZodSchema", () => {

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}>
};`;
Expand Down Expand Up @@ -621,32 +628,32 @@ describe("generateZodSchema", () => {
* @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
*/
Expand Down Expand Up @@ -705,7 +712,7 @@ describe("generateZodSchema", () => {
* @format email, "Should be an email"
*/
enemyEmail: string;
/**
* The email of the superman.
*
Expand Down Expand Up @@ -745,18 +752,18 @@ describe("generateZodSchema", () => {
* @format email should be an email
*/
email: string;
/**
* The name of the hero.
*
* @minLength 2, should be more than 2
* @maxLength 50 should be less than 50
*/
name: string;
/**
* The age of the hero
*
*
* @minimum 0 you are too young
* @maximum 500, "you are too old"
*/
Expand Down
74 changes: 41 additions & 33 deletions src/core/generateZodSchema.ts
Expand Up @@ -805,11 +805,13 @@ function buildZodExtendedSchema(
);
}

zodCall = f.createCallExpression(
f.createPropertyAccessExpression(zodCall, f.createIdentifier("extend")),
undefined,
args
);
if (args?.length) {
zodCall = f.createCallExpression(
f.createPropertyAccessExpression(zodCall, f.createIdentifier("extend")),
undefined,
args
);
}

return withZodProperties(zodCall, properties);
}
Expand Down Expand Up @@ -882,35 +884,41 @@ function buildZodObject({

let objectSchema: ts.CallExpression | undefined;

if (properties.length > 0) {
const parsedProperties = buildZodProperties({
members: properties,
zodImportValue: z,
sourceFile,
dependencies,
getDependencyName,
skipParseJSDoc,
});
const parsedProperties =
properties.length > 0
? buildZodProperties({
members: properties,
zodImportValue: z,
sourceFile,
dependencies,
getDependencyName,
skipParseJSDoc,
})
: undefined;

if (schemaExtensionClauses && schemaExtensionClauses.length > 0) {
objectSchema = buildZodExtendedSchema(schemaExtensionClauses, [
f.createObjectLiteralExpression(
Array.from(parsedProperties.entries()).map(([key, tsCall]) => {
return f.createPropertyAssignment(key, tsCall);
}),
true
),
]);
} else {
objectSchema = buildZodSchema(z, "object", [
f.createObjectLiteralExpression(
Array.from(parsedProperties.entries()).map(([key, tsCall]) => {
return f.createPropertyAssignment(key, tsCall);
}),
true
),
]);
}
if (schemaExtensionClauses && schemaExtensionClauses.length > 0) {
objectSchema = buildZodExtendedSchema(
schemaExtensionClauses,
properties.length > 0
? [
f.createObjectLiteralExpression(
Array.from(parsedProperties!.entries()).map(([key, tsCall]) => {
return f.createPropertyAssignment(key, tsCall);
}),
true
),
]
: undefined
);
} else if (properties.length > 0) {
objectSchema = buildZodSchema(z, "object", [
f.createObjectLiteralExpression(
Array.from(parsedProperties!.entries()).map(([key, tsCall]) => {
return f.createPropertyAssignment(key, tsCall);
}),
true
),
]);
}

if (indexSignature) {
Expand Down
16 changes: 8 additions & 8 deletions src/core/validateGeneratedTypes.test.ts
Expand Up @@ -20,10 +20,10 @@ describe("validateGeneratedTypes", () => {
const integrationTests = {
sourceText: `// Generated by ts-to-zod
import { z } from "zod";
import * as spec from "./${sourceTypes.relativePath.slice(0, -3)}";
import * as generated from "./${zodSchemas.relativePath.slice(0, -3)}";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function expectType<T>(_: T) {
/* noop */
Expand Down Expand Up @@ -66,10 +66,10 @@ describe("validateGeneratedTypes", () => {
const integrationTests = {
sourceText: `// Generated by ts-to-zod
import { z } from "zod";
import * as spec from "./${sourceTypes.relativePath.slice(0, -3)}";
import * as generated from "./${zodSchemas.relativePath.slice(0, -3)}";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function expectType<T>(_: T) {
/* noop */
Expand Down Expand Up @@ -126,10 +126,10 @@ describe("validateGeneratedTypes", () => {
const integrationTests = {
sourceText: `// Generated by ts-to-zod
import { z } from "zod";
import * as spec from "./${sourceTypes.relativePath.slice(0, -3)}";
import * as generated from "./${zodSchemas.relativePath.slice(0, -3)}";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function expectType<T>(_: T) {
/* noop */
Expand Down Expand Up @@ -179,10 +179,10 @@ describe("validateGeneratedTypes", () => {
const integrationTests = {
sourceText: `// Generated by ts-to-zod
import { z } from "zod";
import * as spec from "./${sourceTypes.relativePath.slice(0, -3)}";
import * as generated from "./${zodSchemas.relativePath.slice(0, -3)}";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function expectType<T>(_: T) {
/* noop */
Expand Down
4 changes: 2 additions & 2 deletions src/utils/resolveModules.test.ts
Expand Up @@ -32,7 +32,7 @@ describe("resolveModules", () => {

it("should prefix enum", () => {
const sourceText = `export namespace Metropolis {
export enum Superhero {
export enum Superhero {
Superman = "superman",
ClarkKent = "clark_kent",
};
Expand Down Expand Up @@ -67,7 +67,7 @@ describe("resolveModules", () => {
export interface Clark {
hasGlasses: boolean
}
export type SupermanBis = {
fullName: Name;
name: { first: Name; last: Name };
Expand Down

0 comments on commit 4ad2d09

Please sign in to comment.