Repository navigation
Expand file tree
/
Copy pathlast-array-element.d.ts
More file actions
96 lines (76 loc) · 3.6 KB
/
Copy pathlast-array-element.d.ts
File metadata and controls
96 lines (76 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import type {If} from './if.d.ts';
import type {Subtract} from './subtract.d.ts';
import type {IfNotAnyOrNever, IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts';
import type {SplitOnRestElement} from './split-on-rest-element.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
/**
Extract the type of the last element of an array.
Use-case: Defining the return type of functions that extract the last element of an array, for example [`lodash.last`](https://lodash.com/docs/4.17.15#last).
@example
```
import type {LastArrayElement} from 'type-fest';
declare function lastOf<const V extends readonly any[]>(array: V): LastArrayElement<V>;
const last1 = lastOf(['foo', 'bar']);
//=> 'bar'
const last2 = lastOf([true, false, 'baz', 10]);
//=> 10
```
Note: When the array ends with an optional or rest element, the last element's position becomes ambiguous. In such cases, the result is a union of the types of all elements that could potentially be the last element of the array.
@example
```
import type {LastArrayElement} from 'type-fest';
type A = LastArrayElement<[string, number?, bigint?]>;
//=> bigint | number | string
type B = LastArrayElement<[string, number, bigint?, ...boolean[]]>;
//=> boolean | bigint | number
```
Note: If empty array is a valid value for the array type, the result includes an `undefined`. This aligns with the runtime behavior of `[].at(-1)`.
@example
```
import type {LastArrayElement} from 'type-fest';
type A = LastArrayElement<[]>;
//=> undefined
// `[]` is assignable to `string[]`
type B = LastArrayElement<string[]>;
//=> string | undefined
// `[]` is assignable to `[string?, number?]`
type C = LastArrayElement<[string?, number?]>;
//=> number | string | undefined
// `[]` is assignable to [string?, number?, ...bigint[]]`
type D = LastArrayElement<[string?, number?, ...bigint[]]>;
//=> bigint | number | string | undefined
```
@category Array
@category Template literal
*/
export type LastArrayElement<TArray extends UnknownArray> =
IfNotAnyOrNever<TArray, {
ifNot: TArray extends UnknownArray // For distributing `TArray`
? number extends TArray['length']
? _LastArrayElement<TArray>
// A fixed-length tuple can be read directly from the end. A length refinement on an ordinary array has no tuple keys, so it still needs the array fallback.
: '0' extends keyof TArray
? BeforeRestLastElement<TArray>
: _LastArrayElement<TArray>
: never;
}>;
type _LastArrayElement<TArray extends UnknownArray> =
SplitOnRestElement<TArray> extends readonly [infer BeforeRest extends UnknownArray, infer Rest extends UnknownArray, infer AfterRest extends UnknownArray]
? AfterRest extends readonly [...any, infer Last] // Note there are no optional elements in `AfterRest`.
? Last // If there's a `Last` in `AfterRest`, then that's the result.
: Rest[number] | BeforeRestLastElement<BeforeRest> // Otherwise, the result is union of the `Rest` element and the last element in `BeforeRest`.
: never;
type BeforeRestLastElement<BeforeRest extends UnknownArray, Accumulator = never> =
BeforeRest extends readonly []
? Accumulator | undefined
// Compare lengths so optional metadata does not make required tuple elements look optional.
: BeforeRest['length'] extends Required<BeforeRest>['length']
? BeforeRest[Subtract<BeforeRest['length'], 1>] | Accumulator
: BeforeRest extends readonly [...infer Rest, (infer Last)?]
? BeforeRestLastElement<
Rest,
// Add `undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled.
Last | Accumulator | If<IsExactOptionalPropertyTypesEnabled, never, undefined>
>
: never;
export {};