Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve IDE integration for inferred object properties to support Go To Definition, Rename Symbol, etc. #1117

Merged
merged 11 commits into from May 6, 2022
6 changes: 5 additions & 1 deletion deno/build.mjs
Expand Up @@ -23,7 +23,11 @@ const projectRoot = process.cwd();
const nodeSrcRoot = join(projectRoot, "src");
const denoLibRoot = join(projectRoot, "deno", "lib");

const skipList = [join(nodeSrcRoot, "__tests__", "object-in-es5-env.test.ts")];
const skipList = [
join(nodeSrcRoot, "__tests__", "object-in-es5-env.test.ts"),
join(nodeSrcRoot, "__tests__", "languageServerFeatures.test.ts"),
join(nodeSrcRoot, "__tests__", "languageServerFeatures.source.ts"),
];
const walkAndBuild = (/** @type string */ dir) => {
for (const entry of readdirSync(join(nodeSrcRoot, dir), {
withFileTypes: true,
Expand Down
52 changes: 52 additions & 0 deletions deno/lib/__tests__/object.test.ts
Expand Up @@ -216,6 +216,46 @@ test("test inferred merged type", async () => {
f1;
});

test("inferred merged object type with optional properties", async () => {
const Merged = z
.object({ a: z.string(), b: z.string().optional() })
.merge(z.object({ a: z.string().optional(), b: z.string() }));
type Merged = z.infer<typeof Merged>;
const f1: util.AssertEqual<Merged, { a?: string; b: string }> = true;
f1;
});

test("inferred unioned object type with optional properties", async () => {
const Unioned = z.union([
z.object({ a: z.string(), b: z.string().optional() }),
z.object({ a: z.string().optional(), b: z.string() }),
]);
type Unioned = z.infer<typeof Unioned>;
const f1: util.AssertEqual<
Unioned,
{ a: string; b?: string } | { a?: string; b: string }
> = true;
f1;
});

test("inferred partial object type with optional properties", async () => {
const Partial = z
.object({ a: z.string(), b: z.string().optional() })
.partial();
type Partial = z.infer<typeof Partial>;
const f1: util.AssertEqual<Partial, { a?: string; b?: string }> = true;
f1;
});

test("inferred picked object type with optional properties", async () => {
const Picked = z
.object({ a: z.string(), b: z.string().optional() })
.pick({ b: true });
type Picked = z.infer<typeof Picked>;
const f1: util.AssertEqual<Picked, { b?: string }> = true;
f1;
});

test("inferred type for unknown/any keys", () => {
const myType = z.object({
anyOptional: z.any().optional(),
Expand Down Expand Up @@ -308,3 +348,15 @@ test("constructor key", () => {
})
).toThrow();
});

test("constructor key", () => {
const Example = z.object({
prop: z.string(),
opt: z.number().optional(),
arr: z.string().array(),
});

type Example = z.infer<typeof Example>;
const f1: util.AssertEqual<keyof Example, "prop" | "opt" | "arr"> = true;
f1;
});
25 changes: 5 additions & 20 deletions deno/lib/types.ts
Expand Up @@ -1363,17 +1363,12 @@ export namespace objectUtil {
[k in Exclude<keyof U, keyof V>]: U[k];
} & V;

type optionalKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? k : never;
}[keyof T];

// type requiredKeys<T extends object> = Exclude<keyof T, optionalKeys<T>>;
type requiredKeys<T extends object> = {
export type requiredKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];

export type addQuestionMarks<T extends object> = {
[k in optionalKeys<T>]?: T[k];
[k in keyof T]?: T[k];
} & { [k in requiredKeys<T>]: T[k] };

export type identity<T> = T;
Expand All @@ -1398,9 +1393,7 @@ export namespace objectUtil {
};
}

export type extendShape<A, B> = {
[k in Exclude<keyof A, keyof B>]: A[k];
} & { [k in keyof B]: B[k] };
export type extendShape<A, B> = Omit<A, keyof B> & B;

const AugmentFactory =
<Def extends ZodObjectDef>(def: Def) =>
Expand Down Expand Up @@ -1712,11 +1705,7 @@ export class ZodObject<

pick<Mask extends { [k in keyof T]?: true }>(
mask: Mask
): ZodObject<
objectUtil.noNever<{ [k in keyof Mask]: k extends keyof T ? T[k] : never }>,
UnknownKeys,
Catchall
> {
): ZodObject<Pick<T, Extract<keyof T, keyof Mask>>, UnknownKeys, Catchall> {
const shape: any = {};
util.objectKeys(mask).map((key) => {
shape[key] = this.shape[key];
Expand All @@ -1729,11 +1718,7 @@ export class ZodObject<

omit<Mask extends { [k in keyof T]?: true }>(
mask: Mask
): ZodObject<
objectUtil.noNever<{ [k in keyof T]: k extends keyof Mask ? never : T[k] }>,
UnknownKeys,
Catchall
> {
): ZodObject<Omit<T, keyof Mask>, UnknownKeys, Catchall> {
const shape: any = {};
util.objectKeys(this.shape).map((key) => {
if (util.objectKeys(mask).indexOf(key) === -1) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "zod",
"version": "3.14.4",
"version": "3.14.5",
"description": "TypeScript-first schema declaration and validation library with static type inference",
"main": "./lib/index.js",
"types": "./index.d.ts",
Expand Down Expand Up @@ -87,6 +87,7 @@
"pretty-quick": "^3.1.3",
"rollup": "^2.70.1",
"ts-jest": "^27.1.3",
"ts-morph": "^14.0.0",
"ts-node": "^10.7.0",
"tslib": "^2.3.1",
"typescript": "^4.6.2"
Expand Down
76 changes: 76 additions & 0 deletions src/__tests__/languageServerFeatures.source.ts
@@ -0,0 +1,76 @@
import * as z from "../index";

export const filePath = __filename;

// z.object()

export const Test = z.object({
f1: z.number(),
});

export type Test = z.infer<typeof Test>;

export const instanceOfTest: Test = {
f1: 1,
};

// z.object().merge()

export const TestMerge = z
.object({
f2: z.string().optional(),
})
.merge(Test);

export type TestMerge = z.infer<typeof TestMerge>;

export const instanceOfTestMerge: TestMerge = {
f1: 1,
f2: "string",
};

// z.union()

export const TestUnion = z.union([
z.object({
f2: z.string().optional(),
}),
Test,
]);

export type TestUnion = z.infer<typeof TestUnion>;

export const instanceOfTestUnion: TestUnion = {
f1: 1,
f2: "string",
};

// z.object().partial()

export const TestPartial = Test.partial();

export type TestPartial = z.infer<typeof TestPartial>;

export const instanceOfTestPartial: TestPartial = {
f1: 1,
};

// z.object().pick()

export const TestPick = TestMerge.pick({ f1: true });

export type TestPick = z.infer<typeof TestPick>;

export const instanceOfTestPick: TestPick = {
f1: 1,
};

// z.object().omit()

export const TestOmit = TestMerge.omit({ f2: true });

export type TestOmit = z.infer<typeof TestOmit>;

export const instanceOfTestOmit: TestOmit = {
f1: 1,
};