From 1f64736c0d4360476ec2e2fab8f98b64667efa0c Mon Sep 17 00:00:00 2001 From: mmkal Date: Wed, 25 Nov 2020 16:37:45 -0500 Subject: [PATCH 01/27] Deep property get --- readme.md | 1 + source/split.d.ts | 8 +++++ source/utilities.d.ts | 2 ++ test-d/camel-case.ts | 3 +- test-d/get.ts | 50 ++++++++++++++++++++++++++ ts41/camel-case.d.ts | 10 +----- ts41/get.d.ts | 82 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 source/split.d.ts create mode 100644 test-d/get.ts create mode 100644 ts41/get.d.ts diff --git a/readme.md b/readme.md index 714df7868..9f6961c17 100644 --- a/readme.md +++ b/readme.md @@ -97,6 +97,7 @@ Click the type names for complete docs. - [`PascalCase`](ts41/pascal-case.d.ts) – Converts a string literal to pascal-case (`FooBar`) - [`SnakeCase`](ts41/snake-case.d.ts) – Convert a string literal to snake-case (`foo_bar`). - [`DelimiterCase`](ts41/delimiter-case.d.ts) – Convert a string literal to a custom string delimiter casing. +- [`Get`](ts41/get.d.ts) - Gets a deeply-nested property from an object, like lodash's get method. ### Miscellaneous diff --git a/source/split.d.ts b/source/split.d.ts new file mode 100644 index 000000000..f82e362fb --- /dev/null +++ b/source/split.d.ts @@ -0,0 +1,8 @@ +/** +Recursively split a string literal into two parts on the first occurence of the given string, returning an array literal of all the separate parts. +*/ +export type Split = + string extends S ? string[] : + S extends '' ? [] : + S extends `${infer T}${D}${infer U}` ? [T, ...Split] : + [S]; diff --git a/source/utilities.d.ts b/source/utilities.d.ts index c079b06ff..934e0e571 100644 --- a/source/utilities.d.ts +++ b/source/utilities.d.ts @@ -1,3 +1,5 @@ export type UpperCaseCharacters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'X' | 'Y' | 'Z'; export type WordSeparators = '-' | '_' | ' '; + +export type Integers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; diff --git a/test-d/camel-case.ts b/test-d/camel-case.ts index 7c4720673..b6a5a8a9f 100644 --- a/test-d/camel-case.ts +++ b/test-d/camel-case.ts @@ -1,4 +1,5 @@ -import {Split, CamelCase} from '../source/camel-case'; +import {CamelCase} from '../source/camel-case'; +import {Split} from '../source/split'; import {expectType, expectAssignable} from 'tsd'; // Split diff --git a/test-d/get.ts b/test-d/get.ts new file mode 100644 index 000000000..8984cccef --- /dev/null +++ b/test-d/get.ts @@ -0,0 +1,50 @@ +import {Get} from '../ts41/get'; +import {expectType} from 'tsd'; + +interface ApiResponse { + hits: { + hits: Array<{ + _id: string; + _source: { + name: Array<{ + given: string[]; + family: string; + }>; + birthDate: string; + }; + }>; + }; +} + +expectType>([{given: ['Homer', 'J'], family: 'Simpson'}]); +expectType>([{given: ['Homer', 'J'], family: 'Simpson'}]); +expectType>([{given: ['Homer', 'J'], family: 'Simpson'}]); + +expectType>({} as never); + +interface WithTuples { + foo: [ + {bar: number}, + {baz: boolean} + ]; +} + +expectType>(123); +expectType>(123); + +expectType>({} as never); +expectType>({} as never); + +interface WithNumberKeys { + foo: { + 1: { + bar: number; + }; + }; +} + +expectType>(123); +expectType>(123); + +expectType>({} as never); +expectType>({} as never); diff --git a/ts41/camel-case.d.ts b/ts41/camel-case.d.ts index 4476fd302..f3c485cd3 100644 --- a/ts41/camel-case.d.ts +++ b/ts41/camel-case.d.ts @@ -1,13 +1,5 @@ import {WordSeparators} from '../source/utilities'; - -/** -Recursively split a string literal into two parts on the first occurence of the given string, returning an array literal of all the separate parts. -*/ -export type Split = - string extends S ? string[] : - S extends '' ? [] : - S extends `${infer T}${D}${infer U}` ? [T, ...Split] : - [S]; +import {Split} from '../source/split'; /** Step by step takes the first item in an array literal, formats it and adds it to a string literal, and then recursively appends the remainder. diff --git a/ts41/get.d.ts b/ts41/get.d.ts new file mode 100644 index 000000000..47e94d200 --- /dev/null +++ b/ts41/get.d.ts @@ -0,0 +1,82 @@ +import {Split} from '../source/split'; +import {Integers} from '../source/utilities'; + +/** + * Gets a deeply-nested property from an object, like lodash's `get` method. + * + * Use-case: retrieve a property from deep inside an API response or other complex object. + * + * @example + * import { Get } from 'type-fest' + * + * interface ApiResponse { + * hits: { + * hits: Array<{ + * _id: string + * _source: { + * name: Array<{ + * given: string[] + * family: string + * }> + * birthDate: string + * } + * }> + * } + * } + * + * type Name = Get // Array<{ given: string[]; family: string }> + * + * @explanation + * + * This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. + * Then it recursively uses the head key to get the next property of the current object, until there are no keys + * left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples + * and dictionaries with number keys. + */ +export type Get = GetWithPath>; + +/** + * Like @see Get but receives an array of strings as a path parameter. + */ +type GetWithPath = + Keys extends [] + ? T + : Keys extends [infer Head, ...infer Tail] + ? GetWithPath>, Extract> + : never; + +type ToPath = Split, '.'>; + +type FixPathSquareBrackets = + S extends `${infer T}[${infer U}]${infer V}` + ? `${T}.${U}${FixPathSquareBrackets}` + : S; + +type ConsistsOnlyOf = + S extends '' + ? true + : S extends `${C}${infer Tail}` + ? ConsistsOnlyOf + : false; + +type IsInteger = ConsistsOnlyOf; + +type WithStringKeys> = { + [K in `${Extract}`]: T[K] +}; + +/** + * Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, + * and when indexing objects with number keys. + * Returns `never` if `Key` is not a property of `Object`, + */ +type PropertyOf = + Key extends keyof Object + ? Object[Key] + : Object extends Array + ? IsInteger extends true + ? Item + : never + : Key extends keyof WithStringKeys + ? WithStringKeys[Key] + : never; From 010985895358baff631d03945a61449149344322 Mon Sep 17 00:00:00 2001 From: mmkal Date: Wed, 25 Nov 2020 19:39:29 -0500 Subject: [PATCH 02/27] move split to ts41 --- test-d/camel-case.ts | 2 +- ts41/camel-case.d.ts | 2 +- {source => ts41}/split.d.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename {source => ts41}/split.d.ts (100%) diff --git a/test-d/camel-case.ts b/test-d/camel-case.ts index b6a5a8a9f..ebaca6f4c 100644 --- a/test-d/camel-case.ts +++ b/test-d/camel-case.ts @@ -1,5 +1,5 @@ import {CamelCase} from '../source/camel-case'; -import {Split} from '../source/split'; +import {Split} from './split'; import {expectType, expectAssignable} from 'tsd'; // Split diff --git a/ts41/camel-case.d.ts b/ts41/camel-case.d.ts index f3c485cd3..3a1c43fd3 100644 --- a/ts41/camel-case.d.ts +++ b/ts41/camel-case.d.ts @@ -1,5 +1,5 @@ import {WordSeparators} from '../source/utilities'; -import {Split} from '../source/split'; +import {Split} from './split'; /** Step by step takes the first item in an array literal, formats it and adds it to a string literal, and then recursively appends the remainder. diff --git a/source/split.d.ts b/ts41/split.d.ts similarity index 100% rename from source/split.d.ts rename to ts41/split.d.ts From 81e69f477ddb3eafc5c4269fedd3fa1215e2d1cc Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 26 Nov 2020 10:52:19 -0500 Subject: [PATCH 03/27] Move split to utilities file --- test-d/camel-case.ts | 2 +- ts41/camel-case.d.ts | 2 +- ts41/get.d.ts | 2 +- ts41/{split.d.ts => utilities.d.ts} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename ts41/{split.d.ts => utilities.d.ts} (100%) diff --git a/test-d/camel-case.ts b/test-d/camel-case.ts index ebaca6f4c..ae411c5f7 100644 --- a/test-d/camel-case.ts +++ b/test-d/camel-case.ts @@ -1,5 +1,5 @@ import {CamelCase} from '../source/camel-case'; -import {Split} from './split'; +import {Split} from '../ts41/utilities'; import {expectType, expectAssignable} from 'tsd'; // Split diff --git a/ts41/camel-case.d.ts b/ts41/camel-case.d.ts index 3a1c43fd3..4f9a67bc4 100644 --- a/ts41/camel-case.d.ts +++ b/ts41/camel-case.d.ts @@ -1,5 +1,5 @@ import {WordSeparators} from '../source/utilities'; -import {Split} from './split'; +import {Split} from './utilities'; /** Step by step takes the first item in an array literal, formats it and adds it to a string literal, and then recursively appends the remainder. diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 47e94d200..64d3ccf7a 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -1,4 +1,4 @@ -import {Split} from '../source/split'; +import {Split} from './utilities'; import {Integers} from '../source/utilities'; /** diff --git a/ts41/split.d.ts b/ts41/utilities.d.ts similarity index 100% rename from ts41/split.d.ts rename to ts41/utilities.d.ts From 59e15485c94dddc0de801f95b4d9bf6450df011c Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 26 Nov 2020 11:16:31 -0500 Subject: [PATCH 04/27] Improve docs/naming --- readme.md | 2 +- ts41/get.d.ts | 103 ++++++++++++++++++++++++++++---------------------- 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/readme.md b/readme.md index 9f6961c17..81bda6b5c 100644 --- a/readme.md +++ b/readme.md @@ -97,7 +97,7 @@ Click the type names for complete docs. - [`PascalCase`](ts41/pascal-case.d.ts) – Converts a string literal to pascal-case (`FooBar`) - [`SnakeCase`](ts41/snake-case.d.ts) – Convert a string literal to snake-case (`foo_bar`). - [`DelimiterCase`](ts41/delimiter-case.d.ts) – Convert a string literal to a custom string delimiter casing. -- [`Get`](ts41/get.d.ts) - Gets a deeply-nested property from an object, like lodash's get method. +- [`Get`](ts41/get.d.ts) - Get a deeply-nested property from an object using a key path, like Lodash's `.get()` function. ### Miscellaneous diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 64d3ccf7a..725b8416f 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -2,43 +2,9 @@ import {Split} from './utilities'; import {Integers} from '../source/utilities'; /** - * Gets a deeply-nested property from an object, like lodash's `get` method. - * - * Use-case: retrieve a property from deep inside an API response or other complex object. - * - * @example - * import { Get } from 'type-fest' - * - * interface ApiResponse { - * hits: { - * hits: Array<{ - * _id: string - * _source: { - * name: Array<{ - * given: string[] - * family: string - * }> - * birthDate: string - * } - * }> - * } - * } - * - * type Name = Get // Array<{ given: string[]; family: string }> - * - * @explanation - * - * This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. - * Then it recursively uses the head key to get the next property of the current object, until there are no keys - * left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples - * and dictionaries with number keys. +Like @see Get but receives an array of strings as a path parameter. */ -export type Get = GetWithPath>; - -/** - * Like @see Get but receives an array of strings as a path parameter. - */ -type GetWithPath = +type GetWithPath = Keys extends [] ? T : Keys extends [infer Head, ...infer Tail] @@ -61,22 +27,69 @@ type ConsistsOnlyOf = type IsInteger = ConsistsOnlyOf; +/** +Convert a type which may have number keys to one with string keys, making it possible to index +using strings retrieved from template types. + +@example +``` +type WithNumbers = { foo: string; 0: boolean } +type WithStrings = WithStringKeys + +type WithNumbersKeys = keyof WithNumbers // evaluates to 'foo' | 0 +type WithStringsKeys = keyof WithStrings // evaluates to 'foo' | '0' +``` + */ type WithStringKeys> = { [K in `${Extract}`]: T[K] }; /** - * Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, - * and when indexing objects with number keys. - * Returns `never` if `Key` is not a property of `Object`, +Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, +and when indexing objects with number keys. +Returns `never` if `Key` is not a property of `Object`, */ -type PropertyOf = - Key extends keyof Object - ? Object[Key] - : Object extends Array +type PropertyOf = + Key extends keyof ObjectType + ? ObjectType[Key] + : ObjectType extends Array ? IsInteger extends true ? Item : never - : Key extends keyof WithStringKeys - ? WithStringKeys[Key] + : Key extends keyof WithStringKeys + ? WithStringKeys[Key] : never; + +/** +Gets a deeply-nested property from an object, like lodash's `get` method. + +Use-case: retrieve a property from deep inside an API response or other complex object. + +@example +``` +import { Get } from 'type-fest' + +interface ApiResponse { + hits: { + hits: Array<{ + _id: string + _source: { + name: Array<{ + given: string[] + family: string + }> + birthDate: string + } + }> + } +} + +type Name = Get // Array<{ given: string[]; family: string }> +``` + +This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. +Then it recursively uses the head key to get the next property of the current object, until there are no keys +left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples +and dictionaries with number keys. + */ +export type Get = GetWithPath>; From 9b7634107e58f71c7fe92d82680a7e1bf4f9b3ad Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 26 Nov 2020 11:48:18 -0500 Subject: [PATCH 05/27] Return undefined for bad paths, not never --- ts41/get.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 725b8416f..97f71f918 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -47,7 +47,7 @@ type WithStringKeys> = { /** Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. -Returns `never` if `Key` is not a property of `Object`, +Returns `neundefinedver` if `Key` is not a property of `Object`, */ type PropertyOf = Key extends keyof ObjectType @@ -55,10 +55,10 @@ type PropertyOf = : ObjectType extends Array ? IsInteger extends true ? Item - : never + : undefined : Key extends keyof WithStringKeys ? WithStringKeys[Key] - : never; + : undefined; /** Gets a deeply-nested property from an object, like lodash's `get` method. From 49c60e5ef42c67582ff746c01603c1b709b6a50f Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 26 Nov 2020 11:48:37 -0500 Subject: [PATCH 06/27] use expect-type for clearer assertions --- package.json | 1 + test-d/get.ts | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 7290999b4..fb5c27f89 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "~0.7.0", + "expect-type": "^0.9.0", "tsd": "^0.13.1", "typescript": "^4.1.2", "xo": "^0.35.0" diff --git a/test-d/get.ts b/test-d/get.ts index 8984cccef..66f5fc9b3 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -1,5 +1,5 @@ import {Get} from '../ts41/get'; -import {expectType} from 'tsd'; +import {expectTypeOf} from 'expect-type'; interface ApiResponse { hits: { @@ -16,11 +16,10 @@ interface ApiResponse { }; } -expectType>([{given: ['Homer', 'J'], family: 'Simpson'}]); -expectType>([{given: ['Homer', 'J'], family: 'Simpson'}]); -expectType>([{given: ['Homer', 'J'], family: 'Simpson'}]); +expectTypeOf>().toEqualTypeOf>(); +expectTypeOf>().toEqualTypeOf>(); -expectType>({} as never); +expectTypeOf>().toBeUndefined(); interface WithTuples { foo: [ @@ -29,11 +28,11 @@ interface WithTuples { ]; } -expectType>(123); -expectType>(123); +expectTypeOf>().toBeNumber(); +expectTypeOf>().toBeNumber(); -expectType>({} as never); -expectType>({} as never); +expectTypeOf>().toBeUndefined(); +expectTypeOf>().toBeUndefined(); interface WithNumberKeys { foo: { @@ -43,8 +42,8 @@ interface WithNumberKeys { }; } -expectType>(123); -expectType>(123); +expectTypeOf>().toBeNumber(); +expectTypeOf>().toBeNumber(); -expectType>({} as never); -expectType>({} as never); +expectTypeOf>().toBeUndefined(); +expectTypeOf>().toBeUndefined(); From 859456acaf81fab5585a4aab86a605cac42df9ee Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 27 Nov 2020 18:47:33 +0700 Subject: [PATCH 07/27] Update get.ts --- test-d/get.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index 66f5fc9b3..db0740c4d 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -23,8 +23,12 @@ expectTypeOf>().toBeUndefi interface WithTuples { foo: [ - {bar: number}, - {baz: boolean} + { + bar: number + }, + { + baz: boolean + } ]; } From 874ced047faba777060800ea589245ae4c455add Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 27 Nov 2020 18:53:24 +0700 Subject: [PATCH 08/27] Update get.d.ts --- ts41/get.d.ts | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 97f71f918..13cbb60dc 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -3,7 +3,7 @@ import {Integers} from '../source/utilities'; /** Like @see Get but receives an array of strings as a path parameter. - */ +*/ type GetWithPath = Keys extends [] ? T @@ -28,27 +28,28 @@ type ConsistsOnlyOf = type IsInteger = ConsistsOnlyOf; /** -Convert a type which may have number keys to one with string keys, making it possible to index -using strings retrieved from template types. +Convert a type which may have number keys to one with string keys, making it possible to index using strings retrieved from template types. @example ``` -type WithNumbers = { foo: string; 0: boolean } +type WithNumbers = {foo: string; 0: boolean} type WithStrings = WithStringKeys -type WithNumbersKeys = keyof WithNumbers // evaluates to 'foo' | 0 -type WithStringsKeys = keyof WithStrings // evaluates to 'foo' | '0' +type WithNumbersKeys = keyof WithNumbers +//=> 'foo' | 0 +type WithStringsKeys = keyof WithStrings +//=> 'foo' | '0' ``` - */ +*/ type WithStringKeys> = { [K in `${Extract}`]: T[K] }; /** -Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, -and when indexing objects with number keys. +Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. + Returns `neundefinedver` if `Key` is not a property of `Object`, - */ +*/ type PropertyOf = Key extends keyof ObjectType ? ObjectType[Key] @@ -60,14 +61,15 @@ type PropertyOf = ? WithStringKeys[Key] : undefined; +// This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. Then it recursively uses the head key to get the next property of the current object, until there are no keys left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples and dictionaries with number keys. /** -Gets a deeply-nested property from an object, like lodash's `get` method. +Get a deeply-nested property from an object using a key path, like Lodash's `.get()` function. -Use-case: retrieve a property from deep inside an API response or other complex object. +Use-case: Retrieve a property from deep inside an API response or some other complex object. @example ``` -import { Get } from 'type-fest' +import {Get} from 'type-fest'; interface ApiResponse { hits: { @@ -84,12 +86,8 @@ interface ApiResponse { } } -type Name = Get // Array<{ given: string[]; family: string }> +type Name = Get; +//=> Array<{given: string[]; family: string}> ``` - -This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. -Then it recursively uses the head key to get the next property of the current object, until there are no keys -left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples -and dictionaries with number keys. - */ +*/ export type Get = GetWithPath>; From 54e97b53a4f31fe20128c2a82b166e2ac4f15528 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 27 Nov 2020 18:55:10 +0700 Subject: [PATCH 09/27] Update get.d.ts --- ts41/get.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 13cbb60dc..bb0af89d9 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -32,12 +32,12 @@ Convert a type which may have number keys to one with string keys, making it pos @example ``` -type WithNumbers = {foo: string; 0: boolean} -type WithStrings = WithStringKeys +type WithNumbers = {foo: string; 0: boolean}; +type WithStrings = WithStringKeys; -type WithNumbersKeys = keyof WithNumbers +type WithNumbersKeys = keyof WithNumbers; //=> 'foo' | 0 -type WithStringsKeys = keyof WithStrings +type WithStringsKeys = keyof WithStrings; //=> 'foo' | '0' ``` */ From 87974e1fcdced8f67085fcd0faa05fe695d1593c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 27 Nov 2020 18:57:55 +0700 Subject: [PATCH 10/27] Update get.d.ts --- ts41/get.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index bb0af89d9..53f496341 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -46,7 +46,7 @@ type WithStringKeys> = { }; /** -Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. +Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. Returns `neundefinedver` if `Key` is not a property of `Object`, */ From 8cae0d5f9f87ecd9cad782fbd9f46650594f72cf Mon Sep 17 00:00:00 2001 From: mmkal Date: Sat, 28 Nov 2020 15:42:16 -0500 Subject: [PATCH 11/27] Add some tests; return unknown for bad paths --- test-d/get.ts | 23 ++++++++++++++++++----- ts41/get.d.ts | 23 +++++++++++++---------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index db0740c4d..bd62b338e 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -19,7 +19,8 @@ interface ApiResponse { expectTypeOf>().toEqualTypeOf>(); expectTypeOf>().toEqualTypeOf>(); -expectTypeOf>().toBeUndefined(); +// TypeScript is structurally typed - it's _possible_ this value exists even though it's not on the parent interface, so the type is `unknown`. +expectTypeOf>().toBeUnknown(); interface WithTuples { foo: [ @@ -35,8 +36,8 @@ interface WithTuples { expectTypeOf>().toBeNumber(); expectTypeOf>().toBeNumber(); -expectTypeOf>().toBeUndefined(); -expectTypeOf>().toBeUndefined(); +expectTypeOf>().toBeUnknown(); +expectTypeOf>().toBeUnknown(); interface WithNumberKeys { foo: { @@ -49,5 +50,17 @@ interface WithNumberKeys { expectTypeOf>().toBeNumber(); expectTypeOf>().toBeNumber(); -expectTypeOf>().toBeUndefined(); -expectTypeOf>().toBeUndefined(); +expectTypeOf>().toBeUnknown(); +expectTypeOf>().toBeUnknown(); + +interface WithModifiers { + foo: ReadonlyArray<{ + bar?: { + readonly baz: { + qux: number; + }; + }; + }>; +} + +expectTypeOf>().toEqualTypeOf<{ qux: number }>(); diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 53f496341..676416bdf 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -46,20 +46,19 @@ type WithStringKeys> = { }; /** -Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. - -Returns `neundefinedver` if `Key` is not a property of `Object`, -*/ +Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. +Returns `unknown` if `Key` is not a property of `ObjectType`, + */ type PropertyOf = Key extends keyof ObjectType ? ObjectType[Key] - : ObjectType extends Array + : ObjectType extends ArrayLike ? IsInteger extends true ? Item - : undefined + : unknown : Key extends keyof WithStringKeys ? WithStringKeys[Key] - : undefined; + : unknown; // This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. Then it recursively uses the head key to get the next property of the current object, until there are no keys left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples and dictionaries with number keys. /** @@ -69,7 +68,11 @@ Use-case: Retrieve a property from deep inside an API response or some other com @example ``` -import {Get} from 'type-fest'; +import { Get } from 'type-fest'; +import * as lodash from 'lodash'; + +const get = (object: ObjectType, path: Path): Get => + lodash.get(object, path); interface ApiResponse { hits: { @@ -86,8 +89,8 @@ interface ApiResponse { } } -type Name = Get; -//=> Array<{given: string[]; family: string}> +const getName = (apiResponse: ApiResponse) => + get(apiResponse, 'hits.hits[0]._source.name'); // returns Array<{ given: string[]; family: string }> ``` */ export type Get = GetWithPath>; From 74571d136f97805071138e04d90d9279af972c89 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sun, 29 Nov 2020 10:27:54 -0500 Subject: [PATCH 12/27] Rename Integers -> StringDigit and add JSDoc --- source/utilities.d.ts | 2 +- ts41/get.d.ts | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/source/utilities.d.ts b/source/utilities.d.ts index 934e0e571..a6be73ce0 100644 --- a/source/utilities.d.ts +++ b/source/utilities.d.ts @@ -2,4 +2,4 @@ export type UpperCaseCharacters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' export type WordSeparators = '-' | '_' | ' '; -export type Integers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; +export type StringDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 676416bdf..79e6d04eb 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -1,5 +1,5 @@ import {Split} from './utilities'; -import {Integers} from '../source/utilities'; +import {StringDigit} from '../source/utilities'; /** Like @see Get but receives an array of strings as a path parameter. @@ -11,6 +11,15 @@ type GetWithPath = ? GetWithPath>, Extract> : never; +/** +Splits a dot-prop style path into a tuple comprised of the properties in the path. Handles square-bracket notation. + +@example +``` +ToPath<'foo.bar.baz'> // ['foo', 'bar', 'baz'] +ToPath<'foo[0].bar.baz'> // ['foo', '0', 'bar', 'baz'] +``` +*/ type ToPath = Split, '.'>; type FixPathSquareBrackets = @@ -18,6 +27,16 @@ type FixPathSquareBrackets = ? `${T}.${U}${FixPathSquareBrackets}` : S; +/** +Returns true if S is made up out of C repeated 0 or more times + +@example +``` +ConsistsOnlyOf<'aaa', 'a'> // true +ConsistsOnlyOf<'aBa', 'a'> // false +ConsistsOnlyOf<'', 'a'> // true +``` +*/ type ConsistsOnlyOf = S extends '' ? true @@ -25,7 +44,7 @@ type ConsistsOnlyOf = ? ConsistsOnlyOf : false; -type IsInteger = ConsistsOnlyOf; + type tt = [ConsistsOnlyOf<'', 'ab'>] /** Convert a type which may have number keys to one with string keys, making it possible to index using strings retrieved from template types. @@ -50,10 +69,12 @@ Get a property of an object or array. Works when indexing arrays using number-li Returns `unknown` if `Key` is not a property of `ObjectType`, */ type PropertyOf = - Key extends keyof ObjectType + Object extends null | undefined + ? undefined + : Key extends keyof ObjectType ? ObjectType[Key] : ObjectType extends ArrayLike - ? IsInteger extends true + ? ConsistsOnlyOf extends true ? Item : unknown : Key extends keyof WithStringKeys From 483116f2e1e294abdbed6b8b5394f18f45b6d287 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sun, 29 Nov 2020 10:32:46 -0500 Subject: [PATCH 13/27] Handle optional props --- test-d/get.ts | 14 +++++++++++--- ts41/get.d.ts | 4 +--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index bd62b338e..5987ca49b 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -25,10 +25,10 @@ expectTypeOf>().toBeUnknow interface WithTuples { foo: [ { - bar: number + bar: number; }, { - baz: boolean + baz: boolean; } ]; } @@ -53,6 +53,8 @@ expectTypeOf>().toBeNumber(); expectTypeOf>().toBeUnknown(); expectTypeOf>().toBeUnknown(); +// test readonly, ReadonlyArray, optional props, unions with null + interface WithModifiers { foo: ReadonlyArray<{ bar?: { @@ -60,7 +62,13 @@ interface WithModifiers { qux: number; }; }; + abc: { + def: { + ghi: string; + }; + } | null; }>; } -expectTypeOf>().toEqualTypeOf<{ qux: number }>(); +expectTypeOf>().toEqualTypeOf<{ qux: number } | undefined>(); +expectTypeOf>().toEqualTypeOf(); diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 79e6d04eb..c4ed2a30d 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -44,8 +44,6 @@ type ConsistsOnlyOf = ? ConsistsOnlyOf : false; - type tt = [ConsistsOnlyOf<'', 'ab'>] - /** Convert a type which may have number keys to one with string keys, making it possible to index using strings retrieved from template types. @@ -69,7 +67,7 @@ Get a property of an object or array. Works when indexing arrays using number-li Returns `unknown` if `Key` is not a property of `ObjectType`, */ type PropertyOf = - Object extends null | undefined + ObjectType extends null | undefined ? undefined : Key extends keyof ObjectType ? ObjectType[Key] From 0804a7516888aea9a8824045ab9424bcc47ee526 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sun, 29 Nov 2020 10:38:33 -0500 Subject: [PATCH 14/27] Tab indentation --- test-d/get.ts | 68 +++++++++++++++++++++---------------------- ts41/get.d.ts | 80 +++++++++++++++++++++++++-------------------------- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index 5987ca49b..1bde8bde3 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -2,18 +2,18 @@ import {Get} from '../ts41/get'; import {expectTypeOf} from 'expect-type'; interface ApiResponse { - hits: { - hits: Array<{ - _id: string; - _source: { - name: Array<{ - given: string[]; - family: string; - }>; - birthDate: string; - }; - }>; - }; + hits: { + hits: Array<{ + _id: string; + _source: { + name: Array<{ + given: string[]; + family: string; + }>; + birthDate: string; + }; + }>; + }; } expectTypeOf>().toEqualTypeOf>(); @@ -23,14 +23,14 @@ expectTypeOf>().toEqualTypeOf>().toBeUnknown(); interface WithTuples { - foo: [ - { + foo: [ + { bar: number; }, - { + { baz: boolean; } - ]; + ]; } expectTypeOf>().toBeNumber(); @@ -40,11 +40,11 @@ expectTypeOf>().toBeUnknown(); expectTypeOf>().toBeUnknown(); interface WithNumberKeys { - foo: { - 1: { - bar: number; - }; - }; + foo: { + 1: { + bar: number; + }; + }; } expectTypeOf>().toBeNumber(); @@ -53,21 +53,21 @@ expectTypeOf>().toBeNumber(); expectTypeOf>().toBeUnknown(); expectTypeOf>().toBeUnknown(); -// test readonly, ReadonlyArray, optional props, unions with null +// Test readonly, ReadonlyArray, optional props, unions with null interface WithModifiers { - foo: ReadonlyArray<{ - bar?: { - readonly baz: { - qux: number; - }; - }; - abc: { - def: { - ghi: string; - }; - } | null; - }>; + foo: ReadonlyArray<{ + bar?: { + readonly baz: { + qux: number; + }; + }; + abc: { + def: { + ghi: string; + }; + } | null; + }>; } expectTypeOf>().toEqualTypeOf<{ qux: number } | undefined>(); diff --git a/ts41/get.d.ts b/ts41/get.d.ts index c4ed2a30d..93235f0f9 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -5,11 +5,11 @@ import {StringDigit} from '../source/utilities'; Like @see Get but receives an array of strings as a path parameter. */ type GetWithPath = - Keys extends [] - ? T - : Keys extends [infer Head, ...infer Tail] - ? GetWithPath>, Extract> - : never; + Keys extends [] + ? T + : Keys extends [infer Head, ...infer Tail] + ? GetWithPath>, Extract> + : never; /** Splits a dot-prop style path into a tuple comprised of the properties in the path. Handles square-bracket notation. @@ -23,9 +23,9 @@ ToPath<'foo[0].bar.baz'> // ['foo', '0', 'bar', 'baz'] type ToPath = Split, '.'>; type FixPathSquareBrackets = - S extends `${infer T}[${infer U}]${infer V}` - ? `${T}.${U}${FixPathSquareBrackets}` - : S; + S extends `${infer T}[${infer U}]${infer V}` + ? `${T}.${U}${FixPathSquareBrackets}` + : S; /** Returns true if S is made up out of C repeated 0 or more times @@ -38,11 +38,11 @@ ConsistsOnlyOf<'', 'a'> // true ``` */ type ConsistsOnlyOf = - S extends '' - ? true - : S extends `${C}${infer Tail}` - ? ConsistsOnlyOf - : false; + S extends '' + ? true + : S extends `${C}${infer Tail}` + ? ConsistsOnlyOf + : false; /** Convert a type which may have number keys to one with string keys, making it possible to index using strings retrieved from template types. @@ -59,25 +59,25 @@ type WithStringsKeys = keyof WithStrings; ``` */ type WithStringKeys> = { - [K in `${Extract}`]: T[K] + [K in `${Extract}`]: T[K] }; /** -Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. +Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. Returns `unknown` if `Key` is not a property of `ObjectType`, */ type PropertyOf = - ObjectType extends null | undefined - ? undefined - : Key extends keyof ObjectType - ? ObjectType[Key] - : ObjectType extends ArrayLike - ? ConsistsOnlyOf extends true - ? Item - : unknown - : Key extends keyof WithStringKeys - ? WithStringKeys[Key] - : unknown; + ObjectType extends null | undefined + ? undefined + : Key extends keyof ObjectType + ? ObjectType[Key] + : ObjectType extends ArrayLike + ? ConsistsOnlyOf extends true + ? Item + : unknown + : Key extends keyof WithStringKeys + ? WithStringKeys[Key] + : unknown; // This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. Then it recursively uses the head key to get the next property of the current object, until there are no keys left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples and dictionaries with number keys. /** @@ -91,25 +91,25 @@ import { Get } from 'type-fest'; import * as lodash from 'lodash'; const get = (object: ObjectType, path: Path): Get => - lodash.get(object, path); + lodash.get(object, path); interface ApiResponse { - hits: { - hits: Array<{ - _id: string - _source: { - name: Array<{ - given: string[] - family: string - }> - birthDate: string - } - }> - } + hits: { + hits: Array<{ + _id: string + _source: { + name: Array<{ + given: string[] + family: string + }> + birthDate: string + } + }> + } } const getName = (apiResponse: ApiResponse) => - get(apiResponse, 'hits.hits[0]._source.name'); // returns Array<{ given: string[]; family: string }> + get(apiResponse, 'hits.hits[0]._source.name'); // returns Array<{ given: string[]; family: string }> ``` */ export type Get = GetWithPath>; From d9a64a953aecea2c15d7a5efddb430d62727de53 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sun, 29 Nov 2020 21:47:35 -0500 Subject: [PATCH 15/27] Use words as type names --- test-d/get.ts | 2 ++ ts41/get.d.ts | 31 +++++++++++++++++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index 1bde8bde3..61c00d4c3 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -22,6 +22,7 @@ expectTypeOf>().toEqualTypeOf>().toBeUnknown(); +// This interface uses a tuple type (as opposed to an array) interface WithTuples { foo: [ { @@ -36,6 +37,7 @@ interface WithTuples { expectTypeOf>().toBeNumber(); expectTypeOf>().toBeNumber(); +expectTypeOf>().toBeBoolean(); expectTypeOf>().toBeUnknown(); expectTypeOf>().toBeUnknown(); diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 93235f0f9..e8a084472 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -4,11 +4,11 @@ import {StringDigit} from '../source/utilities'; /** Like @see Get but receives an array of strings as a path parameter. */ -type GetWithPath = +type GetWithPath = Keys extends [] - ? T + ? ObjectType : Keys extends [infer Head, ...infer Tail] - ? GetWithPath>, Extract> + ? GetWithPath>, Extract> : never; /** @@ -22,10 +22,13 @@ ToPath<'foo[0].bar.baz'> // ['foo', '0', 'bar', 'baz'] */ type ToPath = Split, '.'>; -type FixPathSquareBrackets = - S extends `${infer T}[${infer U}]${infer V}` - ? `${T}.${U}${FixPathSquareBrackets}` - : S; +/** +Replaces square-bracketed dot notation with dots, e.g. `foo[0].bar` -> `foo.0.bar` +*/ +type FixPathSquareBrackets = + Path extends `${infer Head}[${infer Middle}]${infer Tail}` + ? `${Head}.${Middle}${FixPathSquareBrackets}` + : Path; /** Returns true if S is made up out of C repeated 0 or more times @@ -37,12 +40,12 @@ ConsistsOnlyOf<'aBa', 'a'> // false ConsistsOnlyOf<'', 'a'> // true ``` */ -type ConsistsOnlyOf = - S extends '' +type ConsistsOnlyOf = + LongString extends '' ? true - : S extends `${C}${infer Tail}` - ? ConsistsOnlyOf - : false; + : LongString extends `${Substring}${infer Tail}` + ? ConsistsOnlyOf + : false; /** Convert a type which may have number keys to one with string keys, making it possible to index using strings retrieved from template types. @@ -58,8 +61,8 @@ type WithStringsKeys = keyof WithStrings; //=> 'foo' | '0' ``` */ -type WithStringKeys> = { - [K in `${Extract}`]: T[K] +type WithStringKeys> = { + [Key in `${Extract}`]: ObjectType[Key] }; /** From 8ee134f97b303e9a9bbcd1917cbee5c1f8adda09 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sun, 29 Nov 2020 22:00:19 -0500 Subject: [PATCH 16/27] Make api response test more realistic --- test-d/get.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index 61c00d4c3..379cb3758 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -1,6 +1,8 @@ import {Get} from '../ts41/get'; import {expectTypeOf} from 'expect-type'; +declare const get: (object: ObjectType, path: Path) => Get; + interface ApiResponse { hits: { hits: Array<{ @@ -16,11 +18,15 @@ interface ApiResponse { }; } -expectTypeOf>().toEqualTypeOf>(); -expectTypeOf>().toEqualTypeOf>(); +declare const apiResponse: ApiResponse; + +expectTypeOf(get(apiResponse, 'hits.hits[0]._source.name')).toEqualTypeOf>(); +expectTypeOf(get(apiResponse, 'hits.hits.0._source.name')).toEqualTypeOf>(); + +expectTypeOf(get(apiResponse, 'hits.hits[0]._source.name[0].given[0]')).toBeString(); // TypeScript is structurally typed - it's _possible_ this value exists even though it's not on the parent interface, so the type is `unknown`. -expectTypeOf>().toBeUnknown(); +expectTypeOf(get(apiResponse, 'hits.someNonsense.notTheRightPath')).toBeUnknown(); // This interface uses a tuple type (as opposed to an array) interface WithTuples { From d971417e2641d6d71782050b14d96151af60a3bc Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 1 Dec 2020 15:51:39 +0700 Subject: [PATCH 17/27] Update get.d.ts --- ts41/get.d.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index e8a084472..ec5f264a9 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -16,14 +16,17 @@ Splits a dot-prop style path into a tuple comprised of the properties in the pat @example ``` -ToPath<'foo.bar.baz'> // ['foo', 'bar', 'baz'] -ToPath<'foo[0].bar.baz'> // ['foo', '0', 'bar', 'baz'] +ToPath<'foo.bar.baz'> +//=> ['foo', 'bar', 'baz'] + +ToPath<'foo[0].bar.baz'> +//=> ['foo', '0', 'bar', 'baz'] ``` */ type ToPath = Split, '.'>; /** -Replaces square-bracketed dot notation with dots, e.g. `foo[0].bar` -> `foo.0.bar` +Replaces square-bracketed dot notation with dots, for example, `foo[0].bar` -> `foo.0.bar`. */ type FixPathSquareBrackets = Path extends `${infer Head}[${infer Middle}]${infer Tail}` @@ -31,13 +34,13 @@ type FixPathSquareBrackets = : Path; /** -Returns true if S is made up out of C repeated 0 or more times +Returns true if `S` is made up out of `C` repeated 0 or more times. @example ``` -ConsistsOnlyOf<'aaa', 'a'> // true -ConsistsOnlyOf<'aBa', 'a'> // false -ConsistsOnlyOf<'', 'a'> // true +ConsistsOnlyOf<'aaa', 'a'> //=> true +ConsistsOnlyOf<'aBa', 'a'> //=> false +ConsistsOnlyOf<'', 'a'> //=> true ``` */ type ConsistsOnlyOf = @@ -66,9 +69,10 @@ type WithStringKeys> = { }; /** -Get a property of an object or array. Works when indexing arrays using number-literal-strings, e.g. `PropertyOf = number`, and when indexing objects with number keys. -Returns `unknown` if `Key` is not a property of `ObjectType`, - */ +Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. + +Returns `unknown` if `Key` is not a property of `ObjectType`. +*/ type PropertyOf = ObjectType extends null | undefined ? undefined @@ -90,7 +94,7 @@ Use-case: Retrieve a property from deep inside an API response or some other com @example ``` -import { Get } from 'type-fest'; +import {Get} from 'type-fest'; import * as lodash from 'lodash'; const get = (object: ObjectType, path: Path): Get => @@ -112,7 +116,8 @@ interface ApiResponse { } const getName = (apiResponse: ApiResponse) => - get(apiResponse, 'hits.hits[0]._source.name'); // returns Array<{ given: string[]; family: string }> + get(apiResponse, 'hits.hits[0]._source.name'); + //=> Array<{given: string[]; family: string}> ``` */ export type Get = GetWithPath>; From 8476a89ef09c54256f70a306b58fb1351ffa3aa5 Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 3 Dec 2020 11:01:40 -0500 Subject: [PATCH 18/27] Fix @see tag --- ts41/get.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index e8a084472..f8349b4d5 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -2,7 +2,7 @@ import {Split} from './utilities'; import {StringDigit} from '../source/utilities'; /** -Like @see Get but receives an array of strings as a path parameter. +Like @see {@link Get} but receives an array of strings as a path parameter. */ type GetWithPath = Keys extends [] From 6078ae931e0c702000a128307726e77a946a3dc0 Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 3 Dec 2020 11:25:16 -0500 Subject: [PATCH 19/27] ObjectType -> BaseType --- ts41/get.d.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 257c7b806..a555966b2 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -4,11 +4,11 @@ import {StringDigit} from '../source/utilities'; /** Like @see {@link Get} but receives an array of strings as a path parameter. */ -type GetWithPath = +type GetWithPath = Keys extends [] - ? ObjectType + ? BaseType : Keys extends [infer Head, ...infer Tail] - ? GetWithPath>, Extract> + ? GetWithPath>, Extract> : never; /** @@ -64,26 +64,26 @@ type WithStringsKeys = keyof WithStrings; //=> 'foo' | '0' ``` */ -type WithStringKeys> = { - [Key in `${Extract}`]: ObjectType[Key] +type WithStringKeys> = { + [Key in `${Extract}`]: BaseType[Key] }; /** Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. -Returns `unknown` if `Key` is not a property of `ObjectType`. +Returns `unknown` if `Key` is not a property of `BaseType`. */ -type PropertyOf = - ObjectType extends null | undefined +type PropertyOf = + BaseType extends null | undefined ? undefined - : Key extends keyof ObjectType - ? ObjectType[Key] - : ObjectType extends ArrayLike + : Key extends keyof BaseType + ? BaseType[Key] + : BaseType extends ArrayLike ? ConsistsOnlyOf extends true ? Item : unknown - : Key extends keyof WithStringKeys - ? WithStringKeys[Key] + : Key extends keyof WithStringKeys + ? WithStringKeys[Key] : unknown; // This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. Then it recursively uses the head key to get the next property of the current object, until there are no keys left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples and dictionaries with number keys. @@ -97,7 +97,7 @@ Use-case: Retrieve a property from deep inside an API response or some other com import {Get} from 'type-fest'; import * as lodash from 'lodash'; -const get = (object: ObjectType, path: Path): Get => +const get = (object: BaseType, path: Path): Get => lodash.get(object, path); interface ApiResponse { @@ -120,4 +120,4 @@ const getName = (apiResponse: ApiResponse) => //=> Array<{given: string[]; family: string}> ``` */ -export type Get = GetWithPath>; +export type Get = GetWithPath>; From 54eb53240a7583a7cd05719813793981d2da8ab1 Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 3 Dec 2020 11:26:43 -0500 Subject: [PATCH 20/27] Improve ConsistsOnlyOf docs --- ts41/get.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index a555966b2..ed05de344 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -34,11 +34,12 @@ type FixPathSquareBrackets = : Path; /** -Returns true if `S` is made up out of `C` repeated 0 or more times. +Returns true if `LongString` is made up out of `Substring` repeated 0 or more times. @example ``` ConsistsOnlyOf<'aaa', 'a'> //=> true +ConsistsOnlyOf<'ababab', 'ab'> //=> true ConsistsOnlyOf<'aBa', 'a'> //=> false ConsistsOnlyOf<'', 'a'> //=> true ``` From 84529f78a068edb859776ef55477996d6409c623 Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 3 Dec 2020 11:34:47 -0500 Subject: [PATCH 21/27] Add to PropertyOf docs --- readme.md | 2 +- ts41/get.d.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 81bda6b5c..f9c2899a8 100644 --- a/readme.md +++ b/readme.md @@ -97,7 +97,7 @@ Click the type names for complete docs. - [`PascalCase`](ts41/pascal-case.d.ts) – Converts a string literal to pascal-case (`FooBar`) - [`SnakeCase`](ts41/snake-case.d.ts) – Convert a string literal to snake-case (`foo_bar`). - [`DelimiterCase`](ts41/delimiter-case.d.ts) – Convert a string literal to a custom string delimiter casing. -- [`Get`](ts41/get.d.ts) - Get a deeply-nested property from an object using a key path, like Lodash's `.get()` function. +- [`Get`](ts41/get.d.ts) - Get a deeply-nested property from an object using a key path, like [Lodash's `.get()`](https://lodash.com/docs/latest#get) function. ### Miscellaneous diff --git a/ts41/get.d.ts b/ts41/get.d.ts index ed05de344..059207f75 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -72,7 +72,9 @@ type WithStringKeys> = { /** Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. -Returns `unknown` if `Key` is not a property of `BaseType`. +Note: +- Returns `unknown` if `Key` is not a property of `BaseType`, since typescript uses structural typing, and it can't be guaranteed that extra properties unknown to the type system will exist at runtime. +- Returns `undefined` from nullish values, to match the behaviour of most deep-key libraries like lodash, dot-prop etc. */ type PropertyOf = BaseType extends null | undefined @@ -80,9 +82,11 @@ type PropertyOf = : Key extends keyof BaseType ? BaseType[Key] : BaseType extends ArrayLike - ? ConsistsOnlyOf extends true - ? Item - : unknown + ? ( + ConsistsOnlyOf extends true + ? Item + : unknown + ) : Key extends keyof WithStringKeys ? WithStringKeys[Key] : unknown; From fff3b74614ae24c20c5fb9c98d3db6046f91eefe Mon Sep 17 00:00:00 2001 From: mmkal Date: Thu, 3 Dec 2020 11:52:11 -0500 Subject: [PATCH 22/27] Don't use ArrayLike --- test-d/get.ts | 1 - ts41/get.d.ts | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index 379cb3758..0d099d5a7 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -45,7 +45,6 @@ expectTypeOf>().toBeNumber(); expectTypeOf>().toBeBoolean(); expectTypeOf>().toBeUnknown(); -expectTypeOf>().toBeUnknown(); interface WithNumberKeys { foo: { diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 059207f75..59e270bc7 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -81,7 +81,10 @@ type PropertyOf = ? undefined : Key extends keyof BaseType ? BaseType[Key] - : BaseType extends ArrayLike + : BaseType extends { + [n: number]: infer Item; + length: number; // Note - this is needed to avoid being too lax with records types using number keys like { 0: string; 1: boolean } + } ? ( ConsistsOnlyOf extends true ? Item From 3f0f49f0912566f362caeba4c226adde0ef9e48b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 20 Dec 2020 14:28:55 +0700 Subject: [PATCH 23/27] Update get.ts --- test-d/get.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-d/get.ts b/test-d/get.ts index 0d099d5a7..9422e7070 100644 --- a/test-d/get.ts +++ b/test-d/get.ts @@ -25,10 +25,10 @@ expectTypeOf(get(apiResponse, 'hits.hits.0._source.name')).toEqualTypeOf>().toBeNumber(); expectTypeOf>().toBeUnknown(); expectTypeOf>().toBeUnknown(); -// Test readonly, ReadonlyArray, optional props, unions with null +// Test `readonly`, `ReadonlyArray`, optional properties, and unions with null. interface WithModifiers { foo: ReadonlyArray<{ @@ -77,5 +77,5 @@ interface WithModifiers { }>; } -expectTypeOf>().toEqualTypeOf<{ qux: number } | undefined>(); +expectTypeOf>().toEqualTypeOf<{qux: number} | undefined>(); expectTypeOf>().toEqualTypeOf(); From cbde1cafa1fc85ecbecbb7414bd8dd39af6ad3d8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 20 Dec 2020 14:31:05 +0700 Subject: [PATCH 24/27] Update get.d.ts --- ts41/get.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 59e270bc7..322d83bc8 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -73,8 +73,8 @@ type WithStringKeys> = { Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. Note: -- Returns `unknown` if `Key` is not a property of `BaseType`, since typescript uses structural typing, and it can't be guaranteed that extra properties unknown to the type system will exist at runtime. -- Returns `undefined` from nullish values, to match the behaviour of most deep-key libraries like lodash, dot-prop etc. +- Returns `unknown` if `Key` is not a property of `BaseType`, since TypeScript uses structural typing, and it cannot be guaranteed that extra properties unknown to the type system will exist at runtime. +- Returns `undefined` from nullish values, to match the behaviour of most deep-key libraries like `lodash`, `dot-prop`, etc. */ type PropertyOf = BaseType extends null | undefined @@ -83,7 +83,7 @@ type PropertyOf = ? BaseType[Key] : BaseType extends { [n: number]: infer Item; - length: number; // Note - this is needed to avoid being too lax with records types using number keys like { 0: string; 1: boolean } + length: number; // Note: This is needed to avoid being too lax with records types using number keys like `{0: string; 1: boolean}`. } ? ( ConsistsOnlyOf extends true From 35ecd37aa5f2c5a8aefb3c7cb9b1bdc971e68733 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sat, 2 Jan 2021 14:00:45 -0500 Subject: [PATCH 25/27] Bump expect-type --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d479d0e0b..a6427ad0b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "~0.7.0", - "expect-type": "^0.9.0", + "expect-type": "^0.11.0", "tsd": "^0.14.0", "typescript": "^4.1.3", "xo": "^0.36.1" From 6560c29027620934a3ebf7e2abb69c5f2404a03f Mon Sep 17 00:00:00 2001 From: mmkal Date: Sat, 2 Jan 2021 14:00:54 -0500 Subject: [PATCH 26/27] Update @see comment --- ts41/get.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index 322d83bc8..da0cacf60 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -2,7 +2,7 @@ import {Split} from './utilities'; import {StringDigit} from '../source/utilities'; /** -Like @see {@link Get} but receives an array of strings as a path parameter. +Like @see Get but receives an array of strings as a path parameter. */ type GetWithPath = Keys extends [] From 4e3d34a867ef074caf06f4ef7c17489f97520d74 Mon Sep 17 00:00:00 2001 From: mmkal Date: Sun, 3 Jan 2021 07:43:55 -0500 Subject: [PATCH 27/27] See no evil --- ts41/get.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts41/get.d.ts b/ts41/get.d.ts index da0cacf60..9103e9aa3 100644 --- a/ts41/get.d.ts +++ b/ts41/get.d.ts @@ -2,7 +2,7 @@ import {Split} from './utilities'; import {StringDigit} from '../source/utilities'; /** -Like @see Get but receives an array of strings as a path parameter. +Like the `Get` type but receives an array of strings as a path parameter. */ type GetWithPath = Keys extends []