Skip to content

Commit c2bf374

Browse files
jarpoolesindresorhusKaribash
authored
Add RequiredDeep type (#614)
Co-authored-by: Sindre Sorhus <[email protected]> Co-authored-by: Karibash <[email protected]>
1 parent bba240f commit c2bf374

File tree

4 files changed

+199
-0
lines changed

4 files changed

+199
-0
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type {
2525
} from './source/omit-index-signature';
2626
export type {PickIndexSignature} from './source/pick-index-signature';
2727
export type {PartialDeep, PartialDeepOptions} from './source/partial-deep';
28+
export type {RequiredDeep} from './source/required-deep';
2829
export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './source/partial-on-undefined-deep';
2930
export type {ReadonlyDeep} from './source/readonly-deep';
3031
export type {LiteralUnion} from './source/literal-union';

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Click the type names for complete docs.
108108
- [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given keys.
109109
- [`RequireExactlyOne`](source/require-exactly-one.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more.
110110
- [`RequireAllOrNone`](source/require-all-or-none.d.ts) - Create a type that requires all of the given keys or none of the given keys.
111+
- [`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.
111112
- [`OmitIndexSignature`](source/omit-index-signature.d.ts) - Omit any index signatures from the given object type, leaving only explicitly defined properties.
112113
- [`PickIndexSignature`](source/pick-index-signature.d.ts) - Pick only index signatures from the given object type, leaving out all explicitly defined properties.
113114
- [`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.

source/required-deep.d.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type {BuiltIns, HasMultipleCallSignatures} from './internal';
2+
3+
type ExcludeUndefined<T> = Exclude<T, undefined>;
4+
5+
/**
6+
Create a type from another type with all keys and nested keys set to required.
7+
8+
Use-cases:
9+
- Creating optional configuration interfaces where the underlying implementation still requires all options to be fully specified.
10+
- Modeling the resulting type after a deep merge with a set of defaults.
11+
12+
@example
13+
```
14+
import type {RequiredDeep} from 'type-fest';
15+
16+
type Settings = {
17+
textEditor?: {
18+
fontSize?: number | undefined;
19+
fontColor?: string | undefined;
20+
fontWeight?: number | undefined;
21+
}
22+
autocomplete?: boolean | undefined;
23+
autosave?: boolean | undefined;
24+
};
25+
26+
type RequiredSettings = RequiredDeep<Settings>;
27+
// type RequiredSettings = {
28+
// textEditor: {
29+
// fontSize: number;
30+
// fontColor: string;
31+
// fontWeight: number;
32+
// }
33+
// autocomplete: boolean;
34+
// autosave: boolean;
35+
// }
36+
```
37+
38+
Note that types containing overloaded functions are not made deeply required due to a [TypeScript limitation](https://github.com/microsoft/TypeScript/issues/29732).
39+
40+
@category Utilities
41+
@category Object
42+
@category Array
43+
@category Set
44+
@category Map
45+
*/
46+
export type RequiredDeep<T, E extends ExcludeUndefined<T> = ExcludeUndefined<T>> = E extends BuiltIns
47+
? E
48+
: E extends Map<infer KeyType, infer ValueType>
49+
? Map<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
50+
: E extends Set<infer ItemType>
51+
? Set<RequiredDeep<ItemType>>
52+
: E extends ReadonlyMap<infer KeyType, infer ValueType>
53+
? ReadonlyMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
54+
: E extends ReadonlySet<infer ItemType>
55+
? ReadonlySet<RequiredDeep<ItemType>>
56+
: E extends WeakMap<infer KeyType, infer ValueType>
57+
? WeakMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
58+
: E extends WeakSet<infer ItemType>
59+
? WeakSet<RequiredDeep<ItemType>>
60+
: E extends Promise<infer ValueType>
61+
? Promise<RequiredDeep<ValueType>>
62+
: E extends (...args: any[]) => unknown
63+
? {} extends RequiredObjectDeep<E>
64+
? E
65+
: HasMultipleCallSignatures<E> extends true
66+
? E
67+
: ((...arguments: Parameters<E>) => ReturnType<E>) & RequiredObjectDeep<E>
68+
: E extends object
69+
? E extends Array<infer ItemType> // Test for arrays/tuples, per https://github.com/microsoft/TypeScript/issues/35156
70+
? ItemType[] extends E // Test for arrays (non-tuples) specifically
71+
? Array<RequiredDeep<ItemType>> // Recreate relevant array type to prevent eager evaluation of circular reference
72+
: RequiredObjectDeep<E> // Tuples behave properly
73+
: RequiredObjectDeep<E>
74+
: unknown;
75+
76+
type RequiredObjectDeep<ObjectType extends object> = {
77+
[KeyType in keyof ObjectType]-?: RequiredDeep<ObjectType[KeyType]>
78+
};

test-d/required-deep.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {expectType} from 'tsd';
2+
import {expectTypeOf} from 'expect-type';
3+
import type {RequiredDeep} from '../index';
4+
5+
type Foo = {
6+
baz?: string | undefined;
7+
bar?: {
8+
function?: ((...args: any[]) => void) | undefined;
9+
functionFixedArity?: ((arg1: unknown, arg2: unknown) => void);
10+
functionWithOverload?: {
11+
(arg: number): string;
12+
(arg1: string, arg2: number): number;
13+
};
14+
namespace?: {
15+
(arg: number): string;
16+
key: string | undefined;
17+
};
18+
namespaceWithOverload: {
19+
(arg: number): string;
20+
(arg1: string, arg2: number): number;
21+
key: string | undefined;
22+
};
23+
object?: {key?: 'value'} | undefined;
24+
string?: string | undefined;
25+
number?: number | undefined;
26+
boolean?: false | undefined;
27+
date?: Date | undefined;
28+
regexp?: RegExp | undefined;
29+
symbol?: Symbol | undefined;
30+
null?: null | undefined;
31+
undefined?: undefined;
32+
map?: Map<string | undefined, string | undefined>;
33+
set?: Set<string | undefined>;
34+
array?: Array<string | undefined>;
35+
tuple?: ['foo' | undefined] | undefined;
36+
readonlyMap?: ReadonlyMap<string | undefined, string | undefined>;
37+
readonlySet?: ReadonlySet<string | undefined>;
38+
readonlyArray?: ReadonlyArray<string | undefined>;
39+
readonlyTuple?: readonly ['foo' | undefined] | undefined;
40+
weakMap?: WeakMap<{key: string | undefined}, string | undefined>;
41+
weakSet?: WeakSet<{key: string | undefined}>;
42+
promise?: Promise<string | undefined>;
43+
};
44+
};
45+
46+
type FooRequired = {
47+
baz: string;
48+
bar: {
49+
function: (...args: any[]) => void;
50+
functionFixedArity: (arg1: unknown, arg2: unknown) => void;
51+
functionWithOverload: {
52+
(arg: number): string;
53+
(arg1: string, arg2: number): number;
54+
};
55+
namespace: {
56+
(arg: number): string;
57+
key: string;
58+
};
59+
namespaceWithOverload: {
60+
(arg: number): string;
61+
(arg1: string, arg2: number): number;
62+
key: string;
63+
};
64+
object: {key: 'value'};
65+
string: string;
66+
number: number;
67+
boolean: false;
68+
date: Date;
69+
regexp: RegExp;
70+
symbol: Symbol;
71+
null: null;
72+
undefined: never;
73+
map: Map<string, string>;
74+
set: Set<string>;
75+
array: string[];
76+
tuple: ['foo'];
77+
readonlyMap: ReadonlyMap<string, string>;
78+
readonlySet: ReadonlySet<string>;
79+
readonlyArray: readonly string[];
80+
readonlyTuple: readonly ['foo'];
81+
weakMap: WeakMap<{key: string}, string>;
82+
weakSet: WeakSet<{key: string}>;
83+
promise: Promise<string>;
84+
};
85+
};
86+
87+
type FooBar = Exclude<Foo['bar'], undefined>;
88+
type FooRequiredBar = FooRequired['bar'];
89+
90+
expectTypeOf<RequiredDeep<Foo>>().toEqualTypeOf<FooRequired>();
91+
expectTypeOf<RequiredDeep<FooBar['function']>>().toEqualTypeOf<FooRequiredBar['function']>();
92+
expectTypeOf<RequiredDeep<FooBar['functionFixedArity']>>().toEqualTypeOf<FooRequiredBar['functionFixedArity']>();
93+
expectTypeOf<RequiredDeep<FooBar['object']>>().toEqualTypeOf<FooRequiredBar['object']>();
94+
expectTypeOf<RequiredDeep<FooBar['string']>>().toEqualTypeOf<FooRequiredBar['string']>();
95+
expectTypeOf<RequiredDeep<FooBar['number']>>().toEqualTypeOf<FooRequiredBar['number']>();
96+
expectTypeOf<RequiredDeep<FooBar['boolean']>>().toEqualTypeOf<FooRequiredBar['boolean']>();
97+
expectTypeOf<RequiredDeep<FooBar['date']>>().toEqualTypeOf<FooRequiredBar['date']>();
98+
expectTypeOf<RequiredDeep<FooBar['regexp']>>().toEqualTypeOf<FooRequiredBar['regexp']>();
99+
expectTypeOf<RequiredDeep<FooBar['map']>>().toEqualTypeOf<FooRequiredBar['map']>();
100+
expectTypeOf<RequiredDeep<FooBar['set']>>().toEqualTypeOf<FooRequiredBar['set']>();
101+
expectTypeOf<RequiredDeep<FooBar['array']>>().toEqualTypeOf<FooRequiredBar['array']>();
102+
expectTypeOf<RequiredDeep<FooBar['tuple']>>().toEqualTypeOf<FooRequiredBar['tuple']>();
103+
expectTypeOf<RequiredDeep<FooBar['readonlyMap']>>().toEqualTypeOf<FooRequiredBar['readonlyMap']>();
104+
expectTypeOf<RequiredDeep<FooBar['readonlySet']>>().toEqualTypeOf<FooRequiredBar['readonlySet']>();
105+
expectTypeOf<RequiredDeep<FooBar['readonlyArray']>>().toEqualTypeOf<FooRequiredBar['readonlyArray']>();
106+
expectTypeOf<RequiredDeep<FooBar['readonlyTuple']>>().toEqualTypeOf<FooRequiredBar['readonlyTuple']>();
107+
expectTypeOf<RequiredDeep<FooBar['weakMap']>>().toEqualTypeOf<FooRequiredBar['weakMap']>();
108+
expectTypeOf<RequiredDeep<FooBar['weakSet']>>().toEqualTypeOf<FooRequiredBar['weakSet']>();
109+
expectTypeOf<RequiredDeep<FooBar['promise']>>().toEqualTypeOf<FooRequiredBar['promise']>();
110+
expectTypeOf<RequiredDeep<FooBar['namespace']>>().toEqualTypeOf<FooRequiredBar['namespace']>();
111+
expectTypeOf<RequiredDeep<FooBar['undefined']>>().toBeNever();
112+
expectTypeOf<RequiredDeep<FooBar['null']>>().toEqualTypeOf<FooRequiredBar['null']>();
113+
114+
// These currently need to be left alone due to TypeScript limitations.
115+
// @see https://github.com/microsoft/TypeScript/issues/29732
116+
expectType<string>(({} as unknown as RequiredDeep<FooBar['functionWithOverload']>)(0));
117+
expectType<number>(({} as unknown as RequiredDeep<FooBar['functionWithOverload']>)('foo', 0));
118+
expectType<string>(({} as unknown as RequiredDeep<FooBar['namespaceWithOverload']>)(0));
119+
expectType<number>(({} as unknown as RequiredDeep<FooBar['namespaceWithOverload']>)('foo', 0));

0 commit comments

Comments
 (0)