From eacc768c026b3005af626700197ac8b0071a8f3f Mon Sep 17 00:00:00 2001 From: Aleksey L Date: Sat, 26 Jan 2019 09:51:01 +0200 Subject: [PATCH 1/3] feat: added WritableKeys/ReadonlyKeys mapped types --- src/__snapshots__/mapped-types.spec.ts.snap | 4 ++++ src/index.ts | 2 ++ src/mapped-types.spec.snap.ts | 15 +++++++++++++ src/mapped-types.spec.ts | 15 +++++++++++++ src/mapped-types.ts | 24 +++++++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/src/__snapshots__/mapped-types.spec.ts.snap b/src/__snapshots__/mapped-types.spec.ts.snap index 846b280..e6d6961 100644 --- a/src/__snapshots__/mapped-types.spec.ts.snap +++ b/src/__snapshots__/mapped-types.spec.ts.snap @@ -104,6 +104,8 @@ exports[`PickByValue testType>() 1`] = `"Pic exports[`PromiseType testType>>() 1`] = `"string"`; +exports[`ReadonlyKeys testType>() 1`] = `"\\"a\\""`; + exports[`SetComplement testType>() 1`] = `"\\"1\\""`; exports[`SetDifference testType>() 1`] = `"\\"1\\""`; @@ -119,3 +121,5 @@ exports[`Subtract testType>() 1`] = `"Pick>() 1`] = `"\\"1\\" | \\"4\\""`; exports[`Unionize testType>() 1`] = `"{ name: string; } | { age: number; } | { visible: boolean; }"`; + +exports[`WritableKeys testType>() 1`] = `"\\"b\\""`; diff --git a/src/index.ts b/src/index.ts index be4956a..a1c9de1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,8 @@ export { Subtract, SymmetricDifference, Unionize, + WritableKeys, + ReadonlyKeys, } from './mapped-types'; // deprecated diff --git a/src/mapped-types.spec.snap.ts b/src/mapped-types.spec.snap.ts index 6372bff..b5c1980 100644 --- a/src/mapped-types.spec.snap.ts +++ b/src/mapped-types.spec.snap.ts @@ -21,6 +21,8 @@ import { DeepRequired, DeepNonNullable, DeepPartial, + WritableKeys, + ReadonlyKeys, _DeepNonNullableArray, _DeepNonNullableObject, _DeepReadonlyArray, @@ -39,6 +41,7 @@ type Props = { name: string; age: number; visible: boolean }; type DefaultProps = { age: number }; type NewProps = { age: string; other: string }; type MixedProps = { name: string; setName: (name: string) => void }; +type ReadWriteProps = { readonly a: number; b: string }; /** * Tests @@ -337,3 +340,15 @@ it('DeepPartial', () => { // @dts-jest:pass:snap -> string testType>>(); }); + +// @dts-jest:group WritableKeys +it('WritableKeys', () => { + // @dts-jest:pass:snap -> "b" + testType>(); +}); + +// @dts-jest:group ReadonlyKeys +it('ReadonlyKeys', () => { + // @dts-jest:pass:snap -> "a" + testType>(); +}); diff --git a/src/mapped-types.spec.ts b/src/mapped-types.spec.ts index 3e4d31c..37c4dab 100644 --- a/src/mapped-types.spec.ts +++ b/src/mapped-types.spec.ts @@ -21,6 +21,8 @@ import { DeepRequired, DeepNonNullable, DeepPartial, + WritableKeys, + ReadonlyKeys, _DeepNonNullableArray, _DeepNonNullableObject, _DeepReadonlyArray, @@ -39,6 +41,7 @@ type Props = { name: string; age: number; visible: boolean }; type DefaultProps = { age: number }; type NewProps = { age: string; other: string }; type MixedProps = { name: string; setName: (name: string) => void }; +type ReadWriteProps = { readonly a: number; b: string }; /** * Tests @@ -337,3 +340,15 @@ it('DeepPartial', () => { // @dts-jest:pass:snap testType>>(); }); + +// @dts-jest:group WritableKeys +it('WritableKeys', () => { + // @dts-jest:pass:snap + testType>(); +}); + +// @dts-jest:group ReadonlyKeys +it('ReadonlyKeys', () => { + // @dts-jest:pass:snap + testType>(); +}); diff --git a/src/mapped-types.ts b/src/mapped-types.ts index 6568a33..4b629d4 100644 --- a/src/mapped-types.ts +++ b/src/mapped-types.ts @@ -221,3 +221,27 @@ export interface _DeepPartialArray extends Array> { } export type _DeepPartialObject = { [P in keyof T]?: DeepPartial; }; + +type IfEquals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? A : B; + +/** + * WritableKeys + * @desc get union type of keys that are writable in object type `T` + * Credit: Matt McCutchen + * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript + */ +export type WritableKeys = { + [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P> +}[keyof T]; + +/** + * ReadonlyKeys + * @desc get union type of keys that are readonly in object type `T` + * Credit: Matt McCutchen + * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript + */ +export type ReadonlyKeys = { + [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P> +}[keyof T]; From d38ecb88c0bdf3876aad03d84a38d12cbfae9073 Mon Sep 17 00:00:00 2001 From: Aleksey L Date: Sat, 26 Jan 2019 10:00:54 +0200 Subject: [PATCH 2/3] docs: added WritableKeys/ReadonlyKeys to README --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 967e054..998fb62 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ This gives you the power to prioritize our work and support project contributors * [`Subtract`](#subtractt-t1) * [`Overwrite`](#overwritet-u) * [`Assign`](#assignt-u) +* [`ReadonlyKeys`](#readonlykeyst) +* [`WritableKeys`](#writablekeyst) ## Mapped Types @@ -399,6 +401,38 @@ type ExtendedProps = Assign; [⇧ back to top](#operations-on-objects) +### `ReadonlyKeys` + +Get union type of keys that are readonly in object type `T` + +**Usage:** + +```ts +import { ReadonlyKeys } from 'utility-types'; + +type Props = { readonly foo: string; bar: number }; +type ReadonlyProps = ReadonlyKeys; +// Expect: "foo" +``` + +[⇧ back to top](#operations-on-objects) + +### `WritableKeys` + +Get union type of keys that are writable (not readonly) in object type `T` + +**Usage:** + +```ts +import { WritableKeys } from 'utility-types'; + +type Props = { readonly foo: string; bar: number }; +type WritableProps = WritableKeys; +// Expect: "bar" +``` + +[⇧ back to top](#operations-on-objects) + --- ## Mapped Types From 7b75c8d954c5a3a839c719f33027f4e095bed3c7 Mon Sep 17 00:00:00 2001 From: Aleksey L Date: Sat, 26 Jan 2019 10:04:02 +0200 Subject: [PATCH 3/3] fix: added object constraint --- src/mapped-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mapped-types.ts b/src/mapped-types.ts index 4b629d4..ae63396 100644 --- a/src/mapped-types.ts +++ b/src/mapped-types.ts @@ -232,7 +232,7 @@ type IfEquals = * Credit: Matt McCutchen * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript */ -export type WritableKeys = { +export type WritableKeys = { [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P> }[keyof T]; @@ -242,6 +242,6 @@ export type WritableKeys = { * Credit: Matt McCutchen * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript */ -export type ReadonlyKeys = { +export type ReadonlyKeys = { [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P> }[keyof T];