Skip to content

Commit

Permalink
feat: βž• add union support for Mark* methods (#324)
Browse files Browse the repository at this point in the history
* test: πŸ§ͺ move tests for MarkOptional to separate file

* test: βž• add UnionExample to all Mark* methods

* feat: βž• add union support for Mark* methods

* docs: πŸ“„ changeset
  • Loading branch information
Beraliv committed Aug 7, 2022
1 parent a86c5b5 commit 5989dda
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-plants-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ts-essentials": patch
---

Add union support to all Mark\* methods: `MarkRequired`, `MarkOptional`, `MarkReadonly` and `MarkWritable`
8 changes: 4 additions & 4 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,16 +400,16 @@ type _MergeN<T extends readonly any[], Result> = T extends readonly [infer Head,
export type MergeN<T extends readonly any[]> = _MergeN<T, {}>;

/** Mark some properties as required, leaving others unchanged */
export type MarkRequired<T, RK extends keyof T> = Omit<T, RK> & Required<Pick<T, RK>>;
export type MarkRequired<T, RK extends keyof T> = T extends T ? Omit<T, RK> & Required<Pick<T, RK>> : never;

/** Mark some properties as optional, leaving others unchanged */
export type MarkOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type MarkOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;

/** Mark some properties as readonly, leaving others unchanged */
export type MarkReadonly<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>;
export type MarkReadonly<T, K extends keyof T> = T extends T ? Omit<T, K> & Readonly<Pick<T, K>> : never;

/** Mark some properties as writable, leaving others unchanged */
export type MarkWritable<T, K extends keyof T> = Omit<T, K> & Writable<Pick<T, K>>;
export type MarkWritable<T, K extends keyof T> = T extends T ? Omit<T, K> & Writable<Pick<T, K>> : never;

/** Convert union type to intersection #darkmagic */
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
Expand Down
20 changes: 0 additions & 20 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import {
DeepReadonly,
DeepRequired,
DeepWritable,
StrictExtract,
Dictionary,
DictionaryValues,
MarkOptional,
MarkRequired,
Merge,
MergeN,
NonEmptyObject,
Expand Down Expand Up @@ -855,23 +852,6 @@ function testNonEmptyArray() {
];
}

function testMarkOptional() {
type TestType = {
required1: number;
required2: string;
optional1?: null;
optional2?: boolean;
};
type ExpectedType = {
required1?: number;
required2: string;
optional1?: null;
optional2?: boolean;
};

type Test = Assert<IsExact<MarkOptional<TestType, "required1">, ExpectedType>>;
}

function testMerge() {
type cases = [
Assert<IsExact<Merge<{}, { a: string }>, { a: string }>>,
Expand Down
45 changes: 45 additions & 0 deletions test/mark-optional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { AssertTrue as Assert, IsExact } from "conditional-type-checks";
import { MarkOptional, OptionalKeys, RequiredKeys, WritableKeys } from "../lib";

function testMarkOptional() {
type Example = {
required1: number;
required2: string;
optional1?: null;
optional2?: boolean;
};

type UnionExample = MarkOptional<
Pick<Example, "required1" | "optional1"> | Pick<Example, "required2" | "optional1">,
"optional1"
>;

let unionElementFields: UnionExample = {
required1: 1,
optional1: null,
};

unionElementFields = {
required2: "2",
optional1: null,
};

type cases = [
Assert<IsExact<MarkOptional<Example, never>, Example>>,
Assert<IsExact<MarkOptional<Example, OptionalKeys<Example>>, Example>>,
Assert<IsExact<MarkOptional<Example, RequiredKeys<Example>>, Partial<Example>>>,
Assert<
IsExact<
MarkOptional<Example, "required1">,
{
required1?: number;
required2: string;
optional1?: null;
optional2?: boolean;
}
>
>,
// @ts-expect-error do NOT support union types
MarkOptional<Example | { a: 1 }, "required1">,
];
}
15 changes: 15 additions & 0 deletions test/mark-readonly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ function testMarkReadonly() {
optional2?: boolean;
};

type UnionExample = MarkReadonly<
Pick<Example, "readonly1" | "optional1"> | Pick<Example, "readonly2" | "optional1">,
"optional1"
>;

let unionElementFields: UnionExample = {
readonly2: /\w+/g,
optional1: null,
};

unionElementFields = {
readonly1: new Date(),
optional1: null,
};

type cases = [
Assert<IsExact<MarkReadonly<Example, never>, Example>>,
Assert<IsExact<MarkReadonly<Example, ReadonlyKeys<Example>>, Example>>,
Expand Down
17 changes: 16 additions & 1 deletion test/mark-required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ function testMarkRequired() {
optional2?: boolean;
};

type UnionExample = MarkRequired<
Pick<Example, "readonly1" | "optional1"> | Pick<Example, "readonly2" | "optional1">,
"optional1"
>;

let unionElementFields: UnionExample = {
readonly2: /\w+/g,
optional1: null,
};

unionElementFields = {
readonly1: new Date(),
optional1: null,
};

type cases = [
Assert<IsExact<MarkRequired<Example, never>, Example>>,
Assert<IsExact<MarkRequired<Example, RequiredKeys<Example>>, Example>>,
Expand All @@ -28,7 +43,7 @@ function testMarkRequired() {
}
>
>,
// @ts-expect-error do NOT support union types
// @ts-expect-error: throws type error when one of union elements doesn't have property
MarkRequired<Example | { a: 1 }, "readonly1">,
];
}
15 changes: 15 additions & 0 deletions test/mark-writable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ function testMarkWritable() {
optional2?: boolean;
};

type UnionExample = MarkWritable<
Pick<Example, "readonly1" | "optional1"> | Pick<Example, "readonly2" | "optional1">,
"optional1"
>;

let unionElementFields: UnionExample = {
readonly2: /\w+/g,
optional1: null,
};

unionElementFields = {
readonly1: new Date(),
optional1: null,
};

type cases = [
Assert<IsExact<MarkWritable<Example, never>, Example>>,
Assert<IsExact<MarkWritable<Example, WritableKeys<Example>>, Example>>,
Expand Down

0 comments on commit 5989dda

Please sign in to comment.