Skip to content

Commit c8c6d55

Browse files
authored
Add AllExtend type (#1164)
1 parent a891143 commit c8c6d55

File tree

12 files changed

+384
-84
lines changed

12 files changed

+384
-84
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export type {IsNull} from './source/is-null.d.ts';
143143
export type {IfNull} from './source/if-null.d.ts';
144144
export type {And} from './source/and.d.ts';
145145
export type {Or} from './source/or.d.ts';
146+
export type {AllExtend} from './source/all-extend.d.ts';
146147
export type {NonEmptyTuple} from './source/non-empty-tuple.d.ts';
147148
export type {FindGlobalInstanceType, FindGlobalType} from './source/find-global-type.d.ts';
148149
export type {If} from './source/if.d.ts';

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ Click the type names for complete docs.
178178
- [`DistributedPick`](source/distributed-pick.d.ts) - Picks keys from a type, distributing the operation over a union.
179179
- [`And`](source/and.d.ts) - Returns a boolean for whether two given types are both true.
180180
- [`Or`](source/or.d.ts) - Returns a boolean for whether either of two given types are true.
181+
- [`AllExtend`](source/all-extend.d.ts) - Returns a boolean for whether every element in an array type extends another type.
181182
- [`NonEmptyTuple`](source/non-empty-tuple.d.ts) - Matches any non-empty tuple.
182183
- [`NonEmptyString`](source/non-empty-string.d.ts) - Matches any non-empty string.
183184
- [`FindGlobalType`](source/find-global-type.d.ts) - Tries to find the type of a global with the given name.

source/all-extend.d.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type {If} from './if.d.ts';
2+
import type {CollapseRestElement} from './internal/array.d.ts';
3+
import type {ApplyDefaultOptions} from './internal/object.d.ts';
4+
import type {IfNotAnyOrNever, Not} from './internal/type.d.ts';
5+
import type {IsAny} from './is-any.d.ts';
6+
import type {IsNever} from './is-never.d.ts';
7+
import type {Or} from './or.d.ts';
8+
import type {UnknownArray} from './unknown-array.d.ts';
9+
10+
/**
11+
@see {@link AllExtend}
12+
*/
13+
type AllExtendOptions = {
14+
/**
15+
Consider `never` elements to match the target type only if the target type itself is `never` (or `any`).
16+
17+
- When set to `true` (default), `never` is _not_ treated as a bottom type, instead, it is treated as a type that matches only itself (or `any`).
18+
- When set to `false`, `never` is treated as a bottom type, and behaves as it normally would.
19+
20+
@default true
21+
22+
@example
23+
```
24+
import type {AllExtend} from 'type-fest';
25+
26+
type A = AllExtend<[1, 2, never], number, {strictNever: true}>;
27+
//=> false
28+
29+
type B = AllExtend<[1, 2, never], number, {strictNever: false}>;
30+
//=> true
31+
32+
type C = AllExtend<[never, never], never, {strictNever: true}>;
33+
//=> true
34+
35+
type D = AllExtend<[never, never], never, {strictNever: false}>;
36+
//=> true
37+
38+
type E = AllExtend<['a', 'b', never], any, {strictNever: true}>;
39+
//=> true
40+
41+
type F = AllExtend<['a', 'b', never], any, {strictNever: false}>;
42+
//=> true
43+
44+
type G = AllExtend<[never, 1], never, {strictNever: true}>;
45+
//=> false
46+
47+
type H = AllExtend<[never, 1], never, {strictNever: false}>;
48+
//=> false
49+
```
50+
*/
51+
strictNever?: boolean;
52+
};
53+
54+
type DefaultAllExtendOptions = {
55+
strictNever: true;
56+
};
57+
58+
/**
59+
Returns a boolean for whether every element in an array type extends another type.
60+
61+
@example
62+
```
63+
import type {AllExtend} from 'type-fest';
64+
65+
type A = AllExtend<[1, 2, 3], number>;
66+
//=> true
67+
68+
type B = AllExtend<[1, 2, '3'], number>;
69+
//=> false
70+
71+
type C = AllExtend<[number, number | string], number>;
72+
//=> boolean
73+
74+
type D = AllExtend<[true, boolean, true], true>;
75+
//=> boolean
76+
```
77+
78+
Note: Behaviour of optional elements depend on the `exactOptionalPropertyTypes` compiler option. When the option is disabled, the target type must include `undefined` for a successful match.
79+
80+
```
81+
import type {AllExtend} from 'type-fest';
82+
83+
// `exactOptionalPropertyTypes` enabled
84+
type A = AllExtend<[1?, 2?, 3?], number>;
85+
//=> true
86+
87+
// `exactOptionalPropertyTypes` disabled
88+
type B = AllExtend<[1?, 2?, 3?], number>;
89+
//=> false
90+
91+
// `exactOptionalPropertyTypes` disabled
92+
type C = AllExtend<[1?, 2?, 3?], number | undefined>;
93+
//=> true
94+
```
95+
96+
@see {@link AllExtendOptions}
97+
98+
@category Utilities
99+
@category Array
100+
*/
101+
export type AllExtend<TArray extends UnknownArray, Type, Options extends AllExtendOptions = {}> =
102+
_AllExtend<CollapseRestElement<TArray>, Type, ApplyDefaultOptions<AllExtendOptions, DefaultAllExtendOptions, Options>>;
103+
104+
type _AllExtend<TArray extends UnknownArray, Type, Options extends Required<AllExtendOptions>> = IfNotAnyOrNever<TArray, If<IsAny<Type>, true,
105+
TArray extends readonly [infer First, ...infer Rest]
106+
? IsNever<First> extends true
107+
? Or<IsNever<Type>, Not<Options['strictNever']>> extends true
108+
// If target `Type` is also `never` OR `strictNever` is disabled, recurse further.
109+
? _AllExtend<Rest, Type, Options>
110+
: false
111+
: First extends Type
112+
? _AllExtend<Rest, Type, Options>
113+
: false
114+
: true
115+
>, false, false>;

source/and.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Every} from './internal/array.d.ts';
1+
import type {AllExtend} from './all-extend.d.ts';
22

33
/**
44
Returns a boolean for whether two given types are both true.
@@ -74,4 +74,4 @@ type G = And<never, never>;
7474
7575
@see {@link Or}
7676
*/
77-
export type And<A extends boolean, B extends boolean> = Every<[A, B], true>;
77+
export type And<A extends boolean, B extends boolean> = AllExtend<[A, B], true>;

source/internal/array.d.ts

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type {If} from '../if.d.ts';
2-
import type {IsAny} from '../is-any.d.ts';
32
import type {IsNever} from '../is-never.d.ts';
3+
import type {OptionalKeysOf} from '../optional-keys-of.d.ts';
44
import type {UnknownArray} from '../unknown-array.d.ts';
5-
import type {IfNotAnyOrNever} from './type.d.ts';
5+
import type {IsExactOptionalPropertyTypesEnabled, IfNotAnyOrNever} from './type.d.ts';
66

77
/**
88
Infer the length of the given array `<T>`.
@@ -97,37 +97,62 @@ Returns whether the given array `T` is readonly.
9797
export type IsArrayReadonly<T extends UnknownArray> = If<IsNever<T>, false, T extends unknown[] ? false : true>;
9898

9999
/**
100-
Returns a boolean for whether every element in an array type extends another type.
101-
102-
Note:
103-
- This type is not designed to be used with non-tuple arrays (like `number[]`), tuples with optional elements (like `[1?, 2?, 3?]`), or tuples that contain a rest element (like `[1, 2, ...number[]]`).
104-
- The `never` type does not match the target type unless the target type is `never` or `any`. For example, `Every<[never, never], never>` returns `true`, but `Every<[never, number], number>` returns `false`.
100+
Transforms a tuple type by replacing it's rest element with a single element that has the same type as the rest element, while keeping all the non-rest elements intact.
105101
106102
@example
107103
```
108-
import type {Every} from 'type-fest';
104+
type A = CollapseRestElement<[string, string, ...number[]]>;
105+
//=> [string, string, number]
106+
107+
type B = CollapseRestElement<[...string[], number, number]>;
108+
//=> [string, number, number]
109109
110-
type A = Every<[1, 2, 3], number>;
111-
//=> true
110+
type C = CollapseRestElement<[string, string, ...Array<number | bigint>]>;
111+
//=> [string, string, number | bigint]
112112
113-
type B = Every<[1, 2, '3'], number>;
114-
//=> false
113+
type D = CollapseRestElement<[string, number]>;
114+
//=> [string, number]
115+
```
115116
116-
type C = Every<[number, number | string], number>;
117-
//=> boolean
117+
Note: Optional modifiers (`?`) are removed from elements unless the `exactOptionalPropertyTypes` compiler option is disabled. When disabled, there's an additional `| undefined` for optional elements.
118118
119-
type D = Every<[true, boolean, true], true>;
120-
//=> boolean
119+
@example
120+
```
121+
// `exactOptionalPropertyTypes` enabled
122+
type A = CollapseRestElement<[string?, string?, ...number[]]>;
123+
//=> [string, string, number]
124+
125+
// `exactOptionalPropertyTypes` disabled
126+
type B = CollapseRestElement<[string?, string?, ...number[]]>;
127+
//=> [string | undefined, string | undefined, number]
121128
```
122129
*/
123-
export type Every<TArray extends UnknownArray, Type> = IfNotAnyOrNever<TArray, If<IsAny<Type>, true,
124-
TArray extends readonly [infer First, ...infer Rest]
125-
? IsNever<First> extends true
126-
? IsNever<Type> extends true
127-
? Every<Rest, Type>
128-
: false
129-
: First extends Type
130-
? Every<Rest, Type>
131-
: false
132-
: true
133-
>, false, false>;
130+
export type CollapseRestElement<TArray extends UnknownArray> = IfNotAnyOrNever<TArray, _CollapseRestElement<TArray>>;
131+
132+
type _CollapseRestElement<
133+
TArray extends UnknownArray,
134+
ForwardAccumulator extends UnknownArray = [],
135+
BackwardAccumulator extends UnknownArray = [],
136+
> =
137+
TArray extends UnknownArray // For distributing `TArray`
138+
? keyof TArray & `${number}` extends never
139+
// Enters this branch, if `TArray` is empty (e.g., []),
140+
// or `TArray` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`).
141+
? TArray extends readonly [...infer Rest, infer Last]
142+
? _CollapseRestElement<Rest, ForwardAccumulator, [Last, ...BackwardAccumulator]> // Accumulate elements that are present after the rest element.
143+
: TArray extends readonly []
144+
? [...ForwardAccumulator, ...BackwardAccumulator]
145+
: [...ForwardAccumulator, TArray[number], ...BackwardAccumulator] // Add the rest element between the accumulated elements.
146+
: TArray extends readonly [(infer First)?, ...infer Rest]
147+
? _CollapseRestElement<
148+
Rest,
149+
[
150+
...ForwardAccumulator,
151+
'0' extends OptionalKeysOf<TArray>
152+
? If<IsExactOptionalPropertyTypesEnabled, First, First | undefined> // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled.
153+
: First,
154+
],
155+
BackwardAccumulator
156+
>
157+
: never // Should never happen, since `[(infer First)?, ...infer Rest]` is a top-type for arrays.
158+
: never; // Should never happen

source/internal/type.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,10 @@ type C = IfNotAnyOrNever<never, 'VALID', 'IS_ANY', 'IS_NEVER'>;
9999
*/
100100
export type IfNotAnyOrNever<T, IfNotAnyOrNever, IfAny = any, IfNever = never> =
101101
If<IsAny<T>, IfAny, If<IsNever<T>, IfNever, IfNotAnyOrNever>>;
102+
103+
/*
104+
Indicates the value of `exactOptionalPropertyTypes` compiler option.
105+
*/
106+
export type IsExactOptionalPropertyTypesEnabled = [(string | undefined)?] extends [string?]
107+
? false
108+
: true;

source/is-lowercase.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Every} from './internal/array.d.ts';
1+
import type {AllExtend} from './all-extend.d.ts';
22

33
/**
44
Returns a boolean for whether the given string literal is lowercase.
@@ -17,7 +17,7 @@ IsLowercase<string>;
1717
//=> boolean
1818
```
1919
*/
20-
export type IsLowercase<S extends string> = Every<_IsLowercase<S>, true>;
20+
export type IsLowercase<S extends string> = AllExtend<_IsLowercase<S>, true>;
2121

2222
/**
2323
Loops through each part in the string and returns a boolean array indicating whether each part is lowercase.

source/is-uppercase.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Every} from './internal/array.d.ts';
1+
import type {AllExtend} from './all-extend.d.ts';
22

33
/**
44
Returns a boolean for whether the given string literal is uppercase.
@@ -17,7 +17,7 @@ IsUppercase<string>;
1717
//=> boolean
1818
```
1919
*/
20-
export type IsUppercase<S extends string> = Every<_IsUppercase<S>, true>;
20+
export type IsUppercase<S extends string> = AllExtend<_IsUppercase<S>, true>;
2121

2222
/**
2323
Loops through each part in the string and returns a boolean array indicating whether each part is uppercase.

0 commit comments

Comments
 (0)