From c2bf37491077ee5fbb580d913ba0c80152d65ce0 Mon Sep 17 00:00:00 2001 From: Jared Poole <43940867+jarpoole@users.noreply.github.com> Date: Wed, 17 May 2023 03:52:41 -0400 Subject: [PATCH] Add `RequiredDeep` type (#614) Co-authored-by: Sindre Sorhus Co-authored-by: Karibash <40270352+Karibash@users.noreply.github.com> --- index.d.ts | 1 + readme.md | 1 + source/required-deep.d.ts | 78 +++++++++++++++++++++++++ test-d/required-deep.ts | 119 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 source/required-deep.d.ts create mode 100644 test-d/required-deep.ts diff --git a/index.d.ts b/index.d.ts index 2c5ef9dd6..359c1c5c5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -25,6 +25,7 @@ export type { } from './source/omit-index-signature'; export type {PickIndexSignature} from './source/pick-index-signature'; export type {PartialDeep, PartialDeepOptions} from './source/partial-deep'; +export type {RequiredDeep} from './source/required-deep'; export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './source/partial-on-undefined-deep'; export type {ReadonlyDeep} from './source/readonly-deep'; export type {LiteralUnion} from './source/literal-union'; diff --git a/readme.md b/readme.md index 5fda23d1f..e09c61f6a 100644 --- a/readme.md +++ b/readme.md @@ -108,6 +108,7 @@ Click the type names for complete docs. - [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given keys. - [`RequireExactlyOne`](source/require-exactly-one.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more. - [`RequireAllOrNone`](source/require-all-or-none.d.ts) - Create a type that requires all of the given keys or none of the given keys. +- [`RequiredDeep`](source/required-deep.d.ts) - Create a deeply required version of another type. Use [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) if you only need one level deep. - [`OmitIndexSignature`](source/omit-index-signature.d.ts) - Omit any index signatures from the given object type, leaving only explicitly defined properties. - [`PickIndexSignature`](source/pick-index-signature.d.ts) - Pick only index signatures from the given object type, leaving out all explicitly defined properties. - [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if you only need one level deep. diff --git a/source/required-deep.d.ts b/source/required-deep.d.ts new file mode 100644 index 000000000..ffbac95bb --- /dev/null +++ b/source/required-deep.d.ts @@ -0,0 +1,78 @@ +import type {BuiltIns, HasMultipleCallSignatures} from './internal'; + +type ExcludeUndefined = Exclude; + +/** +Create a type from another type with all keys and nested keys set to required. + +Use-cases: +- Creating optional configuration interfaces where the underlying implementation still requires all options to be fully specified. +- Modeling the resulting type after a deep merge with a set of defaults. + +@example +``` +import type {RequiredDeep} from 'type-fest'; + +type Settings = { + textEditor?: { + fontSize?: number | undefined; + fontColor?: string | undefined; + fontWeight?: number | undefined; + } + autocomplete?: boolean | undefined; + autosave?: boolean | undefined; +}; + +type RequiredSettings = RequiredDeep; +// type RequiredSettings = { +// textEditor: { +// fontSize: number; +// fontColor: string; +// fontWeight: number; +// } +// autocomplete: boolean; +// autosave: boolean; +// } +``` + +Note that types containing overloaded functions are not made deeply required due to a [TypeScript limitation](https://github.com/microsoft/TypeScript/issues/29732). + +@category Utilities +@category Object +@category Array +@category Set +@category Map +*/ +export type RequiredDeep = ExcludeUndefined> = E extends BuiltIns + ? E + : E extends Map + ? Map, RequiredDeep> + : E extends Set + ? Set> + : E extends ReadonlyMap + ? ReadonlyMap, RequiredDeep> + : E extends ReadonlySet + ? ReadonlySet> + : E extends WeakMap + ? WeakMap, RequiredDeep> + : E extends WeakSet + ? WeakSet> + : E extends Promise + ? Promise> + : E extends (...args: any[]) => unknown + ? {} extends RequiredObjectDeep + ? E + : HasMultipleCallSignatures extends true + ? E + : ((...arguments: Parameters) => ReturnType) & RequiredObjectDeep + : E extends object + ? E extends Array // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156 + ? ItemType[] extends E // Test for arrays (non-tuples) specifically + ? Array> // Recreate relevant array type to prevent eager evaluation of circular reference + : RequiredObjectDeep // Tuples behave properly + : RequiredObjectDeep + : unknown; + +type RequiredObjectDeep = { + [KeyType in keyof ObjectType]-?: RequiredDeep +}; diff --git a/test-d/required-deep.ts b/test-d/required-deep.ts new file mode 100644 index 000000000..b593b692a --- /dev/null +++ b/test-d/required-deep.ts @@ -0,0 +1,119 @@ +import {expectType} from 'tsd'; +import {expectTypeOf} from 'expect-type'; +import type {RequiredDeep} from '../index'; + +type Foo = { + baz?: string | undefined; + bar?: { + function?: ((...args: any[]) => void) | undefined; + functionFixedArity?: ((arg1: unknown, arg2: unknown) => void); + functionWithOverload?: { + (arg: number): string; + (arg1: string, arg2: number): number; + }; + namespace?: { + (arg: number): string; + key: string | undefined; + }; + namespaceWithOverload: { + (arg: number): string; + (arg1: string, arg2: number): number; + key: string | undefined; + }; + object?: {key?: 'value'} | undefined; + string?: string | undefined; + number?: number | undefined; + boolean?: false | undefined; + date?: Date | undefined; + regexp?: RegExp | undefined; + symbol?: Symbol | undefined; + null?: null | undefined; + undefined?: undefined; + map?: Map; + set?: Set; + array?: Array; + tuple?: ['foo' | undefined] | undefined; + readonlyMap?: ReadonlyMap; + readonlySet?: ReadonlySet; + readonlyArray?: ReadonlyArray; + readonlyTuple?: readonly ['foo' | undefined] | undefined; + weakMap?: WeakMap<{key: string | undefined}, string | undefined>; + weakSet?: WeakSet<{key: string | undefined}>; + promise?: Promise; + }; +}; + +type FooRequired = { + baz: string; + bar: { + function: (...args: any[]) => void; + functionFixedArity: (arg1: unknown, arg2: unknown) => void; + functionWithOverload: { + (arg: number): string; + (arg1: string, arg2: number): number; + }; + namespace: { + (arg: number): string; + key: string; + }; + namespaceWithOverload: { + (arg: number): string; + (arg1: string, arg2: number): number; + key: string; + }; + object: {key: 'value'}; + string: string; + number: number; + boolean: false; + date: Date; + regexp: RegExp; + symbol: Symbol; + null: null; + undefined: never; + map: Map; + set: Set; + array: string[]; + tuple: ['foo']; + readonlyMap: ReadonlyMap; + readonlySet: ReadonlySet; + readonlyArray: readonly string[]; + readonlyTuple: readonly ['foo']; + weakMap: WeakMap<{key: string}, string>; + weakSet: WeakSet<{key: string}>; + promise: Promise; + }; +}; + +type FooBar = Exclude; +type FooRequiredBar = FooRequired['bar']; + +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toEqualTypeOf(); +expectTypeOf>().toBeNever(); +expectTypeOf>().toEqualTypeOf(); + +// These currently need to be left alone due to TypeScript limitations. +// @see https://github.com/microsoft/TypeScript/issues/29732 +expectType(({} as unknown as RequiredDeep)(0)); +expectType(({} as unknown as RequiredDeep)('foo', 0)); +expectType(({} as unknown as RequiredDeep)(0)); +expectType(({} as unknown as RequiredDeep)('foo', 0));