From 9810171028d209c51cb23e250df5f42f5208eef3 Mon Sep 17 00:00:00 2001 From: Retsam Date: Tue, 8 Jan 2019 16:25:11 -0500 Subject: [PATCH 1/4] feat: add UnionKeys --- src/types/objects.ts | 9 +++++++++ test/objects/UnionKeys.test.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 test/objects/UnionKeys.test.ts diff --git a/src/types/objects.ts b/src/types/objects.ts index c519e58..1a32712 100644 --- a/src/types/objects.ts +++ b/src/types/objects.ts @@ -102,6 +102,15 @@ export type DiffKeys = Diff, Keys>; */ export type HasKey = K extends Keys ? True : False; +/** + * @param T the union to get the keys of + * @returns a union containing all the keys of members of `T` + */ +export type UnionKeys + // Using a conditional here, so that it distributes over members of the union + // See https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types + = T extends any ? keyof T : never; + // ------------- // Manipulations // ------------- diff --git a/test/objects/UnionKeys.test.ts b/test/objects/UnionKeys.test.ts new file mode 100644 index 0000000..2ac768e --- /dev/null +++ b/test/objects/UnionKeys.test.ts @@ -0,0 +1,16 @@ +import test from 'ava'; +import { assert } from '../helpers/assert'; + +import { AllKeys, UnionKeys } from '../../src'; + +test('Can get all keys between objects in a union', t => { + type a = { w: number, x: string }; + type b = { x: number, z: boolean }; + type c = { y: boolean, z: string }; + + type got = UnionKeys; + type expected = 'w' | 'x' | 'y' | 'z'; + + assert(t); + assert(t); +}); From 3e6727ae7eca2b71f803a6f4c88505e4a9c59154 Mon Sep 17 00:00:00 2001 From: Retsam Date: Tue, 8 Jan 2019 16:25:36 -0500 Subject: [PATCH 2/4] feat: add StrictUnion --- src/types/objects.ts | 20 ++++++++++++++++++++ test/objects/StrictUnion.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test/objects/StrictUnion.test.ts diff --git a/src/types/objects.ts b/src/types/objects.ts index 1a32712..9483252 100644 --- a/src/types/objects.ts +++ b/src/types/objects.ts @@ -243,3 +243,23 @@ export interface ConstructorFor { new (...args: any[]): T; prototype: T; } + +// ------ +// Unions +// ------ + +/** + * Makes a union 'strict', such that members are disallowed from including the keys of other members + * For example, `{x: 1, y: 1}` is a valid member of `{x: number} | {y: number}`, + * but it's not a valid member of StrictUnion<{x: number} | {y: number}>. + * @param T a union type + * @returns a the strict version of `T` + */ +export type StrictUnion = _StrictUnionHelper; + +// UnionMember is actually passed as the whole union, but it's used in a distributive conditional +// to refer to each individual member of the union +export type _StrictUnionHelper = + UnionMember extends any ? + UnionMember & Partial, keyof UnionMember>, never>> + : never; diff --git a/test/objects/StrictUnion.test.ts b/test/objects/StrictUnion.test.ts new file mode 100644 index 0000000..d580b85 --- /dev/null +++ b/test/objects/StrictUnion.test.ts @@ -0,0 +1,22 @@ +import test from 'ava'; +import { assert } from '../helpers/assert'; + +import { StrictUnion } from '../../src'; + +test('disallow union members with mixed properties', t => { + type a = { a: number }; + type b = { b: string }; + + type good1 = {a: 1}; + type good2 = {b: "b"}; + type bad = {a: 1, b: "foo"}; + + type isStrict = T extends Array> ? 'Yes' : 'No'; + + type strictUnion = [good1, good2]; + type nonStrictUnion = [good1, good2, bad]; + + assert, 'Yes'>(t); + assert, 'No'>(t); + +}); From f5c6a6fd53040a6c0bdceba6078d99597f3b3d86 Mon Sep 17 00:00:00 2001 From: Retsam Date: Tue, 8 Jan 2019 16:33:13 -0500 Subject: [PATCH 3/4] docs: generate documentation --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93d2456..1de35bf 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ npm install --save-dev simplytyped **[Objects](#objects)** -[AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [Keys](#keys) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [UnionizeProperties](#unionizeproperties) +[_StrictUnionHelper](#_strictunionhelper) - [AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [Keys](#keys) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StrictUnion](#strictunion) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [UnionizeProperties](#unionizeproperties) - [UnionKeys](#unionkeys) **[Utils](#utils)** @@ -53,6 +53,10 @@ npm install --save-dev simplytyped ## Objects +### _StrictUnionHelper + + + ### AllKeys Gets all keys between two objects. ```ts @@ -466,6 +470,31 @@ test('Can get keys that are same between objects', t => { ``` +### StrictUnion +Makes a union 'strict', such that members are disallowed from including the keys of other members +For example, `{x: 1, y: 1}` is a valid member of `{x: number} | {y: number}`, + but it's not a valid member of StrictUnion<{x: number} | {y: number}>. +```ts +test('disallow union members with mixed properties', t => { + type a = { a: number }; + type b = { b: string }; + + type good1 = {a: 1}; + type good2 = {b: "b"}; + type bad = {a: 1, b: "foo"}; + + type isStrict = T extends Array> ? 'Yes' : 'No'; + + type strictUnion = [good1, good2]; + type nonStrictUnion = [good1, good2, bad]; + + assert, 'Yes'>(t); + assert, 'No'>(t); + +}); + +``` + ### StringKeys Typescript 2.9 introduced `number | symbol` as possible results from `keyof any`. For backwards compatibility with objects containing only `string` keys, this will @@ -492,6 +521,23 @@ test('Can get a union of all values in an object', t => { ``` +### UnionKeys + +```ts +test('Can get all keys between objects in a union', t => { + type a = { w: number, x: string }; + type b = { x: number, z: boolean }; + type c = { y: boolean, z: string }; + + type got = UnionKeys; + type expected = 'w' | 'x' | 'y' | 'z'; + + assert(t); + assert(t); +}); + +``` + ## Utils ### NoInfer From ccf97e0bea2253259e567ae8a223752d0cd95764 Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 8 Jan 2019 20:13:27 -0700 Subject: [PATCH 4/4] docs: silence strictUnionHelper from docs --- README.md | 6 +----- src/types/objects.ts | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1de35bf..448eea2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ npm install --save-dev simplytyped **[Objects](#objects)** -[_StrictUnionHelper](#_strictunionhelper) - [AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [Keys](#keys) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StrictUnion](#strictunion) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [UnionizeProperties](#unionizeproperties) - [UnionKeys](#unionkeys) +[AllKeys](#allkeys) - [AllRequired](#allrequired) - [CombineObjects](#combineobjects) - [DeepPartial](#deeppartial) - [DeepReadonly](#deepreadonly) - [DiffKeys](#diffkeys) - [GetKey](#getkey) - [HasKey](#haskey) - [Intersect](#intersect) - [Keys](#keys) - [KeysByType](#keysbytype) - [Merge](#merge) - [ObjectKeys](#objectkeys) - [ObjectType](#objecttype) - [Omit](#omit) - [Optional](#optional) - [Overwrite](#overwrite) - [PlainObject](#plainobject) - [PureKeys](#purekeys) - [Required](#required) - [SharedKeys](#sharedkeys) - [StrictUnion](#strictunion) - [StringKeys](#stringkeys) - [TaggedObject](#taggedobject) - [UnionizeProperties](#unionizeproperties) - [UnionKeys](#unionkeys) **[Utils](#utils)** @@ -53,10 +53,6 @@ npm install --save-dev simplytyped ## Objects -### _StrictUnionHelper - - - ### AllKeys Gets all keys between two objects. ```ts diff --git a/src/types/objects.ts b/src/types/objects.ts index 9483252..d4dab76 100644 --- a/src/types/objects.ts +++ b/src/types/objects.ts @@ -259,6 +259,7 @@ export type StrictUnion = _StrictUnionHelper; // UnionMember is actually passed as the whole union, but it's used in a distributive conditional // to refer to each individual member of the union +/** no-doc */ export type _StrictUnionHelper = UnionMember extends any ? UnionMember & Partial, keyof UnionMember>, never>>