Skip to content

Commit

Permalink
Add RequiredDeep type (#614)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
Co-authored-by: Karibash <[email protected]>
  • Loading branch information
3 people authored May 17, 2023
1 parent bba240f commit c2bf374
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`](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<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if you only need one level deep.
Expand Down
78 changes: 78 additions & 0 deletions source/required-deep.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type {BuiltIns, HasMultipleCallSignatures} from './internal';

type ExcludeUndefined<T> = Exclude<T, undefined>;

/**
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<Settings>;
// 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<T, E extends ExcludeUndefined<T> = ExcludeUndefined<T>> = E extends BuiltIns
? E
: E extends Map<infer KeyType, infer ValueType>
? Map<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
: E extends Set<infer ItemType>
? Set<RequiredDeep<ItemType>>
: E extends ReadonlyMap<infer KeyType, infer ValueType>
? ReadonlyMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
: E extends ReadonlySet<infer ItemType>
? ReadonlySet<RequiredDeep<ItemType>>
: E extends WeakMap<infer KeyType, infer ValueType>
? WeakMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
: E extends WeakSet<infer ItemType>
? WeakSet<RequiredDeep<ItemType>>
: E extends Promise<infer ValueType>
? Promise<RequiredDeep<ValueType>>
: E extends (...args: any[]) => unknown
? {} extends RequiredObjectDeep<E>
? E
: HasMultipleCallSignatures<E> extends true
? E
: ((...arguments: Parameters<E>) => ReturnType<E>) & RequiredObjectDeep<E>
: E extends object
? E extends Array<infer ItemType> // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156
? ItemType[] extends E // Test for arrays (non-tuples) specifically
? Array<RequiredDeep<ItemType>> // Recreate relevant array type to prevent eager evaluation of circular reference
: RequiredObjectDeep<E> // Tuples behave properly
: RequiredObjectDeep<E>
: unknown;

type RequiredObjectDeep<ObjectType extends object> = {
[KeyType in keyof ObjectType]-?: RequiredDeep<ObjectType[KeyType]>
};
119 changes: 119 additions & 0 deletions test-d/required-deep.ts
Original file line number Diff line number Diff line change
@@ -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<string | undefined, string | undefined>;
set?: Set<string | undefined>;
array?: Array<string | undefined>;
tuple?: ['foo' | undefined] | undefined;
readonlyMap?: ReadonlyMap<string | undefined, string | undefined>;
readonlySet?: ReadonlySet<string | undefined>;
readonlyArray?: ReadonlyArray<string | undefined>;
readonlyTuple?: readonly ['foo' | undefined] | undefined;
weakMap?: WeakMap<{key: string | undefined}, string | undefined>;
weakSet?: WeakSet<{key: string | undefined}>;
promise?: Promise<string | undefined>;
};
};

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<string, string>;
set: Set<string>;
array: string[];
tuple: ['foo'];
readonlyMap: ReadonlyMap<string, string>;
readonlySet: ReadonlySet<string>;
readonlyArray: readonly string[];
readonlyTuple: readonly ['foo'];
weakMap: WeakMap<{key: string}, string>;
weakSet: WeakSet<{key: string}>;
promise: Promise<string>;
};
};

type FooBar = Exclude<Foo['bar'], undefined>;
type FooRequiredBar = FooRequired['bar'];

expectTypeOf<RequiredDeep<Foo>>().toEqualTypeOf<FooRequired>();
expectTypeOf<RequiredDeep<FooBar['function']>>().toEqualTypeOf<FooRequiredBar['function']>();
expectTypeOf<RequiredDeep<FooBar['functionFixedArity']>>().toEqualTypeOf<FooRequiredBar['functionFixedArity']>();
expectTypeOf<RequiredDeep<FooBar['object']>>().toEqualTypeOf<FooRequiredBar['object']>();
expectTypeOf<RequiredDeep<FooBar['string']>>().toEqualTypeOf<FooRequiredBar['string']>();
expectTypeOf<RequiredDeep<FooBar['number']>>().toEqualTypeOf<FooRequiredBar['number']>();
expectTypeOf<RequiredDeep<FooBar['boolean']>>().toEqualTypeOf<FooRequiredBar['boolean']>();
expectTypeOf<RequiredDeep<FooBar['date']>>().toEqualTypeOf<FooRequiredBar['date']>();
expectTypeOf<RequiredDeep<FooBar['regexp']>>().toEqualTypeOf<FooRequiredBar['regexp']>();
expectTypeOf<RequiredDeep<FooBar['map']>>().toEqualTypeOf<FooRequiredBar['map']>();
expectTypeOf<RequiredDeep<FooBar['set']>>().toEqualTypeOf<FooRequiredBar['set']>();
expectTypeOf<RequiredDeep<FooBar['array']>>().toEqualTypeOf<FooRequiredBar['array']>();
expectTypeOf<RequiredDeep<FooBar['tuple']>>().toEqualTypeOf<FooRequiredBar['tuple']>();
expectTypeOf<RequiredDeep<FooBar['readonlyMap']>>().toEqualTypeOf<FooRequiredBar['readonlyMap']>();
expectTypeOf<RequiredDeep<FooBar['readonlySet']>>().toEqualTypeOf<FooRequiredBar['readonlySet']>();
expectTypeOf<RequiredDeep<FooBar['readonlyArray']>>().toEqualTypeOf<FooRequiredBar['readonlyArray']>();
expectTypeOf<RequiredDeep<FooBar['readonlyTuple']>>().toEqualTypeOf<FooRequiredBar['readonlyTuple']>();
expectTypeOf<RequiredDeep<FooBar['weakMap']>>().toEqualTypeOf<FooRequiredBar['weakMap']>();
expectTypeOf<RequiredDeep<FooBar['weakSet']>>().toEqualTypeOf<FooRequiredBar['weakSet']>();
expectTypeOf<RequiredDeep<FooBar['promise']>>().toEqualTypeOf<FooRequiredBar['promise']>();
expectTypeOf<RequiredDeep<FooBar['namespace']>>().toEqualTypeOf<FooRequiredBar['namespace']>();
expectTypeOf<RequiredDeep<FooBar['undefined']>>().toBeNever();
expectTypeOf<RequiredDeep<FooBar['null']>>().toEqualTypeOf<FooRequiredBar['null']>();

// These currently need to be left alone due to TypeScript limitations.
// @see https://github.com/microsoft/TypeScript/issues/29732
expectType<string>(({} as unknown as RequiredDeep<FooBar['functionWithOverload']>)(0));
expectType<number>(({} as unknown as RequiredDeep<FooBar['functionWithOverload']>)('foo', 0));
expectType<string>(({} as unknown as RequiredDeep<FooBar['namespaceWithOverload']>)(0));
expectType<number>(({} as unknown as RequiredDeep<FooBar['namespaceWithOverload']>)('foo', 0));

0 comments on commit c2bf374

Please sign in to comment.