Skip to content

Commit

Permalink
Improved Optional type to better handle few edge cases
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrwitek committed May 7, 2019
1 parent 5125de8 commit 8f1adf8
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 60 deletions.
118 changes: 78 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"devDependencies": {
"@types/jest": "21.1.10",
"dts-jest": "22.0.4",
"husky": "1.3.1",
"husky": "2.2.0",
"jest": "21.2.1",
"prettier": "1.17.0",
"ts-jest": "23.10.5",
Expand Down
8 changes: 6 additions & 2 deletions src/__snapshots__/mapped-types.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,13 @@ exports[`OmitByValueExact testType<OmitByValueExact<RequiredOptionalProps, undef
exports[`OmitByValueExact testType<OmitByValueExact<T, number>>() 1`] = `"Pick<T, { [Key in keyof T]: [number] extends [T[Key]] ? [T[Key]] extends [T[Key] & number] ? never : Key : Key; }[keyof T]>"`;
exports[`Optional testType<Optional<Props, 'age' | 'visible'>>() 1`] = `"(Pick<Props, \\"name\\" | \\"visible\\"> & { age?: number | undefined; }) | (Pick<Props, \\"name\\" | \\"age\\"> & { visible?: boolean | undefined; })"`;
exports[`Optional testType<Optional<Props, 'age' | 'visible'>>({ name: 'Yolo' }) 1`] = `"Optional<Props, \\"age\\" | \\"visible\\">"`;
exports[`Optional testType<Optional<Props>>() 1`] = `"Partial<Props>"`;
exports[`Optional testType<Optional<Props, 'age' | 'visible'>>({ name: 'Yolo', age: 99 }) 1`] = `"Optional<Props, \\"age\\" | \\"visible\\">"`;
exports[`Optional testType<Optional<Props>>({ age: 99 }) 1`] = `"Optional<Props, \\"name\\" | \\"age\\" | \\"visible\\">"`;
exports[`Optional testType<Optional<Props>>({}) 1`] = `"Optional<Props, \\"name\\" | \\"age\\" | \\"visible\\">"`;
exports[`OptionalKeys testType<OptionalKeys<RequiredOptionalProps>>() 1`] = `"\\"opt\\" | \\"optUndef\\""`;
Expand Down
14 changes: 9 additions & 5 deletions src/mapped-types.spec.snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,13 @@ type RequiredOptionalProps = {

// @dts-jest:group Optional
{
// @dts-jest:pass:snap -> Partial<Props>
testType<Optional<Props>>();

// @dts-jest:pass:snap -> (Pick<Props, "name" | "visible"> & { age?: number | undefined; }) | (Pick<Props, "name" | "age"> & { visible?: boolean | undefined; })
testType<Optional<Props, 'age' | 'visible'>>();
// @dts-jest:pass:snap -> Optional<Props, "name" | "age" | "visible">
testType<Optional<Props>>({});
// @dts-jest:pass:snap -> Optional<Props, "name" | "age" | "visible">
testType<Optional<Props>>({ age: 99 });

// @dts-jest:pass:snap -> Optional<Props, "age" | "visible">
testType<Optional<Props, 'age' | 'visible'>>({ name: 'Yolo' });
// @dts-jest:pass:snap -> Optional<Props, "age" | "visible">
testType<Optional<Props, 'age' | 'visible'>>({ name: 'Yolo', age: 99 });
}
8 changes: 6 additions & 2 deletions src/mapped-types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,12 @@ type RequiredOptionalProps = {
// @dts-jest:group Optional
{
// @dts-jest:pass:snap
testType<Optional<Props>>();
testType<Optional<Props>>({});
// @dts-jest:pass:snap
testType<Optional<Props>>({ age: 99 });

// @dts-jest:pass:snap
testType<Optional<Props, 'age' | 'visible'>>();
testType<Optional<Props, 'age' | 'visible'>>({ name: 'Yolo' });
// @dts-jest:pass:snap
testType<Optional<Props, 'age' | 'visible'>>({ name: 'Yolo', age: 99 });
}
20 changes: 11 additions & 9 deletions src/mapped-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ export type ReadonlyKeys<T extends object> = {
>
}[keyof T];

type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X
? 1
: 2) extends (<T>() => T extends Y ? 1 : 2)
? A
: B;

/**
* RequiredKeys
* @desc get union type of keys that are required in object type `T`
Expand Down Expand Up @@ -527,12 +533,6 @@ export interface _DeepPartialArray<T> extends Array<DeepPartial<T>> {}
/** @private */
export type _DeepPartialObject<T> = { [P in keyof T]?: DeepPartial<T[P]> };

type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X
? 1
: 2) extends (<T>() => T extends Y ? 1 : 2)
? A
: B;

/**
* Brand
* @desc Define nominal type of U based on type of T.
Expand Down Expand Up @@ -571,6 +571,8 @@ export type Brand<T, U> = T & { __brand: U };
* // Expect: { name: string; age?: number; visible?: boolean; }
* type Props = Optional<Props, 'age' | 'visible'>;
*/
export type Optional<T extends object, K = keyof any> = K extends (keyof T)
? (Omit<T, K> & { [key in K]?: T[key] })
: Partial<T>;
export type Optional<T extends object, K extends keyof T = keyof T> = Omit<
T,
K
> &
Partial<Pick<T, K>>;
2 changes: 1 addition & 1 deletion utils/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @internal */
export function testType<T>(): T {
export function testType<T>(a?: T): T {
return undefined as any;
}

Expand Down

0 comments on commit 8f1adf8

Please sign in to comment.