From 37774694ba8c9ce83a998b317ee560bcff08eb2e Mon Sep 17 00:00:00 2001 From: bbrk24 Date: Wed, 20 Jul 2022 07:26:41 -0400 Subject: [PATCH] Add `recurseIntoArrays` option for `PartialDeep` (#400) Co-authored-by: Sindre Sorhus --- index.d.ts | 2 +- source/partial-deep.d.ts | 64 ++++++++++++++++++++++++++++++---------- test-d/partial-deep.ts | 27 +++++++++++++++++ 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/index.d.ts b/index.d.ts index af1ab44e8..4a93673e5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -14,7 +14,7 @@ export {RequireAtLeastOne} from './source/require-at-least-one'; export {RequireExactlyOne} from './source/require-exactly-one'; export {RequireAllOrNone} from './source/require-all-or-none'; export {RemoveIndexSignature} from './source/remove-index-signature'; -export {PartialDeep} from './source/partial-deep'; +export {PartialDeep, PartialDeepOptions} from './source/partial-deep'; export {ReadonlyDeep} from './source/readonly-deep'; export {LiteralUnion} from './source/literal-union'; export {Promisable} from './source/promisable'; diff --git a/source/partial-deep.d.ts b/source/partial-deep.d.ts index 8ba39fb02..7aeb390a8 100644 --- a/source/partial-deep.d.ts +++ b/source/partial-deep.d.ts @@ -1,5 +1,17 @@ import type {BuiltIns} from './internal'; +/** +@see PartialDeep +*/ +export interface PartialDeepOptions { + /** + Whether to affect the individual elements of arrays and tuples. + + @default true + */ + readonly recurseIntoArrays?: boolean; +} + /** Create a type from another type with all keys and nested keys set to optional. @@ -28,54 +40,74 @@ const applySavedSettings = (savedSettings: PartialDeep) => { settings = applySavedSettings({textEditor: {fontWeight: 500}}); ``` +By default, this also affects array and tuple types: + +``` +import type {PartialDeep} from 'type-fest'; + +interface Settings { + languages: string[]; +} + +const partialSettings: PartialDeep = { + languages: [undefined] +}; +``` + +If this is undesirable, you can pass `{recurseIntoArrays: false}` as the second type argument. + @category Object @category Array @category Set @category Map */ -export type PartialDeep = T extends BuiltIns +export type PartialDeep = T extends BuiltIns ? T : T extends Map - ? PartialMapDeep + ? PartialMapDeep : T extends Set - ? PartialSetDeep + ? PartialSetDeep : T extends ReadonlyMap - ? PartialReadonlyMapDeep + ? PartialReadonlyMapDeep : T extends ReadonlySet - ? PartialReadonlySetDeep + ? PartialReadonlySetDeep : T extends ((...arguments: any[]) => unknown) ? T | undefined : T extends object - ? T extends Array // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156 - ? ItemType[] extends T // Test for arrays (non-tuples) specifically - ? Array> // Recreate relevant array type to prevent eager evaluation of circular reference - : PartialObjectDeep // Tuples behave properly - : PartialObjectDeep + ? T extends ReadonlyArray // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156 + ? Options['recurseIntoArrays'] extends false // If they opt out of array testing, just use the original type + ? T + : ItemType[] extends T // Test for arrays (non-tuples) specifically + ? readonly ItemType[] extends T // Differentiate readonly and mutable arrays + ? ReadonlyArray> + : Array> + : PartialObjectDeep // Tuples behave properly + : PartialObjectDeep : unknown; /** Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`. */ -interface PartialMapDeep extends Map, PartialDeep> {} +interface PartialMapDeep extends Map, PartialDeep> {} /** Same as `PartialDeep`, but accepts only `Set`s as inputs. Internal helper for `PartialDeep`. */ -interface PartialSetDeep extends Set> {} +interface PartialSetDeep extends Set> {} /** Same as `PartialDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `PartialDeep`. */ -interface PartialReadonlyMapDeep extends ReadonlyMap, PartialDeep> {} +interface PartialReadonlyMapDeep extends ReadonlyMap, PartialDeep> {} /** Same as `PartialDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `PartialDeep`. */ -interface PartialReadonlySetDeep extends ReadonlySet> {} +interface PartialReadonlySetDeep extends ReadonlySet> {} /** Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`. */ -type PartialObjectDeep = { - [KeyType in keyof ObjectType]?: PartialDeep +type PartialObjectDeep = { + [KeyType in keyof ObjectType]?: PartialDeep }; diff --git a/test-d/partial-deep.ts b/test-d/partial-deep.ts index 875dfe019..012d3bb8c 100644 --- a/test-d/partial-deep.ts +++ b/test-d/partial-deep.ts @@ -63,3 +63,30 @@ type Recurse = type RecurseObject = {value: Recurse}; const recurseObject: RecurseObject = {value: null}; expectAssignable>(recurseObject); + +// Check that recurseIntoArrays: true is the default +expectType>(partialDeepFoo); +// Check that recurseIntoArrays: false behaves as expected +const partialDeepNoRecurseIntoArraysFoo: PartialDeep = foo; +// These are mostly the same checks as before, but the array/tuple types are different. +expectError(expectType>(partialDeepNoRecurseIntoArraysFoo)); +const partialDeepNoRecurseIntoArraysBar: PartialDeep = foo.bar; +expectType(partialDeepNoRecurseIntoArraysFoo.bar); +expectType<((_: string) => void) | undefined>(partialDeepNoRecurseIntoArraysBar.function); +expectAssignable(partialDeepNoRecurseIntoArraysBar.object); +expectType(partialDeepNoRecurseIntoArraysBar.string); +expectType(partialDeepNoRecurseIntoArraysBar.number); +expectType(partialDeepNoRecurseIntoArraysBar.boolean); +expectType(partialDeepNoRecurseIntoArraysBar.date); +expectType(partialDeepNoRecurseIntoArraysBar.regexp); +expectType(partialDeepNoRecurseIntoArraysBar.symbol); +expectType(partialDeepNoRecurseIntoArraysBar.null); +expectType(partialDeepNoRecurseIntoArraysBar.undefined); +expectAssignable | undefined>(partialDeepNoRecurseIntoArraysBar.map); +expectAssignable | undefined>(partialDeepNoRecurseIntoArraysBar.set); +expectType(partialDeepNoRecurseIntoArraysBar.array); +expectType<['foo'] | undefined>(partialDeepNoRecurseIntoArraysBar.tuple); +expectAssignable | undefined>(partialDeepNoRecurseIntoArraysBar.readonlyMap); +expectAssignable | undefined>(partialDeepNoRecurseIntoArraysBar.readonlySet); +expectType(partialDeepNoRecurseIntoArraysBar.readonlyArray); +expectType(partialDeepNoRecurseIntoArraysBar.readonlyTuple);