Skip to content

Commit 6def351

Browse files
committed
fix: required-deep no longer removes undefined
1 parent 93728b5 commit 6def351

File tree

2 files changed

+89
-138
lines changed

2 files changed

+89
-138
lines changed

source/required-deep.d.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,34 +43,30 @@ Note that types containing overloaded functions are not made deeply required due
4343
@category Set
4444
@category Map
4545
*/
46-
export type RequiredDeep<T, E extends ExcludeUndefined<T> = ExcludeUndefined<T>> = E extends BuiltIns
47-
? E
48-
: E extends Map<infer KeyType, infer ValueType>
46+
export type RequiredDeep<T> = T extends BuiltIns
47+
? T
48+
: T extends Map<infer KeyType, infer ValueType>
4949
? Map<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
50-
: E extends Set<infer ItemType>
50+
: T extends Set<infer ItemType>
5151
? Set<RequiredDeep<ItemType>>
52-
: E extends ReadonlyMap<infer KeyType, infer ValueType>
52+
: T extends ReadonlyMap<infer KeyType, infer ValueType>
5353
? ReadonlyMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
54-
: E extends ReadonlySet<infer ItemType>
54+
: T extends ReadonlySet<infer ItemType>
5555
? ReadonlySet<RequiredDeep<ItemType>>
56-
: E extends WeakMap<infer KeyType, infer ValueType>
56+
: T extends WeakMap<infer KeyType, infer ValueType>
5757
? WeakMap<RequiredDeep<KeyType>, RequiredDeep<ValueType>>
58-
: E extends WeakSet<infer ItemType>
58+
: T extends WeakSet<infer ItemType>
5959
? WeakSet<RequiredDeep<ItemType>>
60-
: E extends Promise<infer ValueType>
60+
: T extends Promise<infer ValueType>
6161
? Promise<RequiredDeep<ValueType>>
62-
: E extends (...arguments_: 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>
62+
: T extends (...arguments_: any[]) => unknown
63+
? {} extends RequiredObjectDeep<T>
64+
? T
65+
: HasMultipleCallSignatures<T> extends true
66+
? T
67+
: ((...arguments_: Parameters<T>) => ReturnType<T>) & RequiredObjectDeep<T>
68+
: T extends object
69+
? RequiredObjectDeep<T>
7470
: unknown;
7571

7672
type RequiredObjectDeep<ObjectType extends object> = {

test-d/required-deep.ts

Lines changed: 72 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,80 @@
11
import {expectType} from 'tsd';
2-
import {expectTypeOf} from 'expect-type';
3-
import type {RequiredDeep} from '../index.d.ts';
2+
import type {RequiredDeep, Simplify} from '../index.d.ts';
3+
import type {BuiltIns} from '../source/internal/type.d.ts';
44

5-
type Foo = {
6-
baz?: string | undefined;
7-
bar?: {
8-
function?: ((...arguments_: any[]) => void) | undefined;
9-
functionFixedArity?: ((argument1: unknown, argument2: unknown) => void);
10-
functionWithOverload?: {
11-
(argument: number): string;
12-
(argument1: string, argument2: number): number;
13-
};
14-
namespace?: {
15-
(argument: number): string;
16-
key: string | undefined;
17-
};
18-
namespaceWithOverload: {
19-
(argument: number): string;
20-
(argument1: string, argument2: 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-
};
5+
expectType<RequiredDeep<{a?: number; b: string}>>({} as {a: number; b: string});
6+
expectType<RequiredDeep<{a?: {b?: {c?: string}; d?: string}}>>({} as {a: {b: {c: string}; d: string}});
7+
expectType<RequiredDeep<{readonly a?: number; readonly b: {c?: string}}>>({} as {readonly a: number; readonly b: {c: string}});
8+
expectType<RequiredDeep<{a?: Array<{b?: number}>}>>({} as {a: Array<{b: number}>});
9+
expectType<RequiredDeep<{a?: [{b?: string}, number]}>>({} as {a: [{b: string}, number]});
10+
expectType<RequiredDeep<{a?: number | {readonly b?: string}}>>({} as {a: number | {readonly b: string}});
11+
expectType<RequiredDeep<{a?: {b: number} | {c?: string}}>>({} as {a: {b: number} | {c: string}});
12+
expectType<RequiredDeep<{a?: {readonly b?: BuiltIns}}>>({} as {a: {readonly b: BuiltIns}});
4513

46-
type FooRequired = {
47-
baz: string;
48-
bar: {
49-
function: (...arguments_: any[]) => void;
50-
functionFixedArity: (argument1: unknown, argument2: unknown) => void;
51-
functionWithOverload: {
52-
(argument: number): string;
53-
(argument1: string, argument2: number): number;
54-
};
55-
namespace: {
56-
(argument: number): string;
57-
key: string;
58-
};
59-
namespaceWithOverload: {
60-
(argument: number): string;
61-
(argument1: string, argument2: 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-
};
14+
// Unions
15+
expectType<RequiredDeep<{a?: number} | {b?: string}>>({} as {a: number} | {b: string});
16+
expectType<RequiredDeep<{v?: {a?: number}} | {w?: {b?: string}}>>({} as {v: {a: number}} | {w: {b: string}});
17+
expectType<RequiredDeep<Map<{a: {b?: {c: {d?: number}}}}, {e?: string}> | Set<{a: {b?: {c: {d?: number}}}}>>>(
18+
{} as Map<{a: {b: {c: {d: number}}}}, {e: string}> | Set<{a: {b: {c: {d: number}}}}>,
19+
);
8620

87-
type FooBar = Exclude<Foo['bar'], undefined>;
88-
type FooRequiredBar = FooRequired['bar'];
21+
// Index signatures
22+
expectType<RequiredDeep<{[x: string]: {a?: string; b: {c?: number}}}>>({} as {[x: string]: {a: string; b: {c: number}}});
23+
expectType<RequiredDeep<{[x: string]: {[x: number]: {c?: number}}}>>({} as {[x: string]: {[x: number]: {c: number}}});
8924

90-
// TODO: Fix this case: https://github.com/mmkal/expect-type/issues/34
91-
// @ts-expect-error
92-
expectTypeOf<RequiredDeep<Foo>>().toEqualTypeOf<FooRequired>();
93-
expectTypeOf<RequiredDeep<FooBar['function']>>().toEqualTypeOf<FooRequiredBar['function']>();
94-
expectTypeOf<RequiredDeep<FooBar['functionFixedArity']>>().toEqualTypeOf<FooRequiredBar['functionFixedArity']>();
95-
expectTypeOf<RequiredDeep<FooBar['object']>>().toEqualTypeOf<FooRequiredBar['object']>();
96-
expectTypeOf<RequiredDeep<FooBar['string']>>().toEqualTypeOf<FooRequiredBar['string']>();
97-
expectTypeOf<RequiredDeep<FooBar['number']>>().toEqualTypeOf<FooRequiredBar['number']>();
98-
expectTypeOf<RequiredDeep<FooBar['boolean']>>().toEqualTypeOf<FooRequiredBar['boolean']>();
99-
expectTypeOf<RequiredDeep<FooBar['date']>>().toEqualTypeOf<FooRequiredBar['date']>();
100-
expectTypeOf<RequiredDeep<FooBar['regexp']>>().toEqualTypeOf<FooRequiredBar['regexp']>();
101-
expectTypeOf<RequiredDeep<FooBar['map']>>().toEqualTypeOf<FooRequiredBar['map']>();
102-
expectTypeOf<RequiredDeep<FooBar['set']>>().toEqualTypeOf<FooRequiredBar['set']>();
103-
expectTypeOf<RequiredDeep<FooBar['array']>>().toEqualTypeOf<FooRequiredBar['array']>();
104-
expectTypeOf<RequiredDeep<FooBar['tuple']>>().toEqualTypeOf<FooRequiredBar['tuple']>();
105-
expectTypeOf<RequiredDeep<FooBar['readonlyMap']>>().toEqualTypeOf<FooRequiredBar['readonlyMap']>();
106-
expectTypeOf<RequiredDeep<FooBar['readonlySet']>>().toEqualTypeOf<FooRequiredBar['readonlySet']>();
107-
expectTypeOf<RequiredDeep<FooBar['readonlyArray']>>().toEqualTypeOf<FooRequiredBar['readonlyArray']>();
108-
expectTypeOf<RequiredDeep<FooBar['readonlyTuple']>>().toEqualTypeOf<FooRequiredBar['readonlyTuple']>();
109-
expectTypeOf<RequiredDeep<FooBar['weakMap']>>().toEqualTypeOf<FooRequiredBar['weakMap']>();
110-
expectTypeOf<RequiredDeep<FooBar['weakSet']>>().toEqualTypeOf<FooRequiredBar['weakSet']>();
111-
expectTypeOf<RequiredDeep<FooBar['promise']>>().toEqualTypeOf<FooRequiredBar['promise']>();
25+
// Optional with `undefined`
26+
// This behaviour changes depending on `exactOptionalPropertyTypes` compiler option, refer https://github.com/sindresorhus/type-fest/issues/1217
27+
expectType<RequiredDeep<{a?: number | undefined}>>({} as {a: number | undefined});
28+
expectType<RequiredDeep<{a?: {b: number} | undefined}>>({} as {a: {b: number} | undefined});
29+
expectType<RequiredDeep<{a?: undefined}>>({} as {a: undefined});
11230

113-
// TODO: Fix this case: https://github.com/mmkal/expect-type/issues/34
114-
// @ts-expect-error
115-
expectTypeOf<RequiredDeep<FooBar['namespace']>>().toEqualTypeOf<FooRequiredBar['namespace']>();
31+
// Tuples
32+
expectType<RequiredDeep<[string, number, boolean]>>({} as [string, number, boolean]); // All required
33+
expectType<RequiredDeep<readonly [string, number?, boolean?]>>({} as readonly [string, number, boolean]); // Required and optional
34+
expectType<RequiredDeep<[string?, number?, boolean?]>>({} as [string, number, boolean]); // All optional
35+
expectType<RequiredDeep<[string, number, ...boolean[]]>>({} as [string, number, ...boolean[]]); // Required and trailing rest
36+
expectType<RequiredDeep<readonly [string, number?, ...boolean[]]>>({} as readonly [string, number, ...boolean[]]); // Required, optional and trailing rest
37+
expectType<RequiredDeep<[string?, number?, ...boolean[]]>>({} as [string, number, ...boolean[]]); // Optional and trailing rest
38+
expectType<RequiredDeep<[...string[], number, boolean]>>({} as [...string[], number, boolean]); // Leading rest
39+
expectType<RequiredDeep<[number, ...string[], number, boolean]>>({} as [number, ...string[], number, boolean]); // Rest in middle
40+
41+
// Nested tuples
42+
expectType<RequiredDeep<[[string, number?], [boolean, Date]?, [RegExp?, number?]?]>>({} as [[string, number], [boolean, Date], [RegExp, number]]);
43+
expectType<RequiredDeep<[[{a?: string}, {b: number; c?: string}?], ...Array<[boolean, boolean?]>]>>({} as [[{a: string}, {b: number; c: string}], ...Array<[boolean, boolean]>]);
44+
45+
// Non-tuple arrays
46+
expectType<RequiredDeep<string[]>>({} as string[]);
47+
expectType<RequiredDeep<Array<string | undefined>>>({} as string[]);
48+
expectType<RequiredDeep<ReadonlyArray<string | undefined>>>({} as readonly string[]);
49+
50+
// Maps
51+
expectType<RequiredDeep<Map<{a?: string; b?: number}, {a?: string; b?: number}>>>({} as Map<{a: string; b: number}, {a: string; b: number}>);
52+
expectType<RequiredDeep<ReadonlyMap<{a?: string; b?: number}, {a?: string; b?: number}>>>({} as ReadonlyMap<{a: string; b: number}, {a: string; b: number}>);
53+
expectType<RequiredDeep<WeakMap<{a?: string; b?: number}, {a?: string; b?: number}>>>({} as WeakMap<{a: string; b: number}, {a: string; b: number}>);
11654

117-
expectTypeOf<RequiredDeep<FooBar['undefined']>>().toBeNever();
118-
expectTypeOf<RequiredDeep<FooBar['null']>>().toEqualTypeOf<FooRequiredBar['null']>();
55+
// Sets
56+
expectType<RequiredDeep<Set<{a?: string; b?: number}>>>({} as Set<{a: string; b: number}>);
57+
expectType<RequiredDeep<ReadonlySet<{a?: string; b?: number}>>>({} as ReadonlySet<{a: string; b: number}>);
58+
expectType<RequiredDeep<WeakSet<{a?: string; b?: number}>>>({} as WeakSet<{a: string; b: number}>);
11959

120-
// These currently need to be left alone due to TypeScript limitations.
121-
// @see https://github.com/microsoft/TypeScript/issues/29732
122-
expectType<string>(({} as unknown as RequiredDeep<FooBar['functionWithOverload']>)(0));
123-
expectType<number>(({} as unknown as RequiredDeep<FooBar['functionWithOverload']>)('foo', 0));
124-
expectType<string>(({} as unknown as RequiredDeep<FooBar['namespaceWithOverload']>)(0));
125-
expectType<number>(({} as unknown as RequiredDeep<FooBar['namespaceWithOverload']>)('foo', 0));
60+
// Functions
61+
type FunctionWithProperties = {(a1: string, a2: number): boolean; p1?: string; readonly p2?: number};
62+
declare const functionWithProperties: RequiredDeep<FunctionWithProperties>;
63+
expectType<boolean>(functionWithProperties('foo', 1));
64+
expectType<{p1: string; readonly p2: number}>({} as Simplify<typeof functionWithProperties>); // `Simplify` removes the call signature from `typeof functionWithProperties`
65+
66+
type FunctionWithProperties2 = {(a1: boolean, ...a2: string[]): number; p1?: {p2?: string; p3: {readonly p4?: boolean}}};
67+
declare const functionWithProperties2: RequiredDeep<FunctionWithProperties2>;
68+
expectType<number>(functionWithProperties2(true, 'foo', 'bar'));
69+
expectType<{p1: {p2: string; p3: {readonly p4: boolean}}}>({} as Simplify<typeof functionWithProperties2>);
70+
71+
type FunctionWithProperties3 = {(): void; p1?: {p2?: string; p3: [{p4?: number}, string?]}};
72+
declare const functionWithProperties3: RequiredDeep<FunctionWithProperties3>;
73+
expectType<void>(functionWithProperties3());
74+
expectType<{p1: {p2: string; p3: [{p4: number}, string]}}>({} as Simplify<typeof functionWithProperties3>);
75+
76+
// Properties with functions containing multiple call signatures are not made required due to a TS limitation, refer https://github.com/microsoft/TypeScript/issues/29732
77+
type FunctionWithProperties4 = {(a1: number): string; (a1: string, a2: number): number; p1?: string};
78+
declare const functionWithProperties4: RequiredDeep<FunctionWithProperties4>;
79+
// @ts-expect-error
80+
expectType<{p1: string}>({} as Simplify<typeof functionWithProperties4>);

0 commit comments

Comments
 (0)