Skip to content

Commit 0e9cfea

Browse files
committed
feat: add reverse type
1 parent 34b8fad commit 0e9cfea

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export type {IsUppercase} from './source/is-uppercase.d.ts';
162162
export type {IsOptional} from './source/is-optional.d.ts';
163163
export type {IsNullable} from './source/is-nullable.d.ts';
164164
export type {TupleOf} from './source/tuple-of.d.ts';
165+
export type {ArrayReverse} from './source/array-reverse.d.ts';
165166

166167
// Template literal types
167168
export type {CamelCase, CamelCaseOptions} from './source/camel-case.d.ts';

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ Click the type names for complete docs.
261261
- [`SplitOnRestElement`](source/split-on-rest-element.d.ts) - Splits an array into three parts, where the first contains all elements before the rest element, the second is the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element itself, and the third contains all elements after the rest element.
262262
- [`ExtractRestElement`](source/extract-rest-element.d.ts) - Extracts the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element type from an array.
263263
- [`ExcludeRestElement`](source/exclude-rest-element.d.ts) - Creates a tuple with the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element removed.
264+
- [`ArrayReverse`](source/array-reverse.d.ts) - Reverses the order of elements in a tuple type.
264265

265266
### Numeric
266267

source/array-reverse.d.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type {If} from './if.d.ts';
2+
import type {IsArrayReadonly} from './internal/array.d.ts';
3+
import type {IfNotAnyOrNever, IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts';
4+
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
5+
import type {UnknownArray} from './unknown-array.d.ts';
6+
7+
/**
8+
Reverses the order of elements in a tuple type.
9+
10+
@example
11+
```ts
12+
import type {ArrayReverse} from 'type-fest';
13+
14+
type A = ArrayReverse<[string, number, boolean]>;
15+
//=> [boolean, number, string]
16+
17+
type B = ArrayReverse<readonly [string, string, ...number[]]>;
18+
//=> readonly [...number[], string, string]
19+
20+
type C = ArrayReverse<["foo", "bar"] | readonly [1, 2, 3]>;
21+
//=> ["bar", "foo"] | readonly [3, 2, 1]
22+
23+
type D = ArrayReverse<string[]>;
24+
//=> string[]
25+
26+
type E = ArrayReverse<[]>;
27+
//=> []
28+
```
29+
30+
Note: If the tuple contains optional elements, the result will be a union of tuples, refer to the examples below:
31+
32+
@example
33+
```ts
34+
import type {ArrayReverse} from 'type-fest';
35+
36+
type A = ArrayReverse<[string, number, boolean?]>;
37+
//=> [boolean, number, string] | [number, string]
38+
39+
type B = ArrayReverse<[string, number?, boolean?]>;
40+
//=> [boolean, number, string] | [number, string] | [string]
41+
42+
type C = ArrayReverse<[string?, number?, boolean?]>;
43+
//=> [boolean, number, string] | [number, string] | [string] | []
44+
45+
type D = ArrayReverse<[string, number?, ...boolean[]]>;
46+
//=> [...boolean[], number, string] | [string]
47+
48+
type E = ArrayReverse<[string?, number?, ...boolean[]]>;
49+
//=> [...boolean[], number, string] | [number, string] | [string] | []
50+
```
51+
52+
@category Array
53+
*/
54+
export type ArrayReverse<TArray extends UnknownArray> = IfNotAnyOrNever<TArray,
55+
TArray extends unknown // For distributing `TArray`
56+
? _ArrayReverse<TArray> extends infer Result
57+
? If<IsArrayReadonly<TArray>, Readonly<Result>, Result>
58+
: never // Should never happen
59+
: never>; // Should never happen
60+
61+
type _ArrayReverse<
62+
TArray extends UnknownArray,
63+
BeforeRestAcc extends UnknownArray = [],
64+
AfterRestAcc extends UnknownArray = [],
65+
Result extends UnknownArray = never,
66+
> =
67+
keyof TArray & `${number}` extends never
68+
// Enters this branch, if `Array_` is empty (e.g., `[]`),
69+
// or `Array_` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`).
70+
? TArray extends readonly [...infer Rest, infer Last]
71+
? _ArrayReverse<Rest, BeforeRestAcc, [...AfterRestAcc, Last], Result> // Accumulate elements that are present after the rest element in reverse order.
72+
: Result | [...AfterRestAcc, ...TArray, ...BeforeRestAcc] // Add the rest element between the accumulated elements.
73+
: TArray extends readonly [(infer First)?, ...infer Rest]
74+
? If<IsOptionalKeyOf<TArray, '0'>,
75+
_ArrayReverse<
76+
Rest,
77+
[First | (If<IsExactOptionalPropertyTypesEnabled, never, undefined>), ...BeforeRestAcc], // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled.
78+
AfterRestAcc,
79+
Result | BeforeRestAcc
80+
>,
81+
_ArrayReverse<Rest, [First, ...BeforeRestAcc], AfterRestAcc, Result>>
82+
: never; // Should never happen, since `readonly [(infer First)?, ...infer Rest]` is a top-type for arrays.
83+
84+
export {};

test-d/array-reverse.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {expectType} from 'tsd';
2+
import type {ArrayReverse} from '../source/array-reverse.d.ts';
3+
4+
// Empty & single-element tuples
5+
expectType<[]>({} as ArrayReverse<[]>);
6+
expectType<readonly []>({} as ArrayReverse<readonly []>);
7+
expectType<[string]>({} as ArrayReverse<[string]>);
8+
expectType<readonly [number]>({} as ArrayReverse<readonly [number]>);
9+
expectType<[any]>({} as ArrayReverse<[any]>);
10+
expectType<[never]>({} as ArrayReverse<[never]>);
11+
12+
// Only required elements
13+
expectType<[3, 2, 1]>({} as ArrayReverse<[1, 2, 3]>);
14+
expectType<readonly [string, number, boolean, bigint]>({} as ArrayReverse<readonly [bigint, boolean, number, string]>);
15+
expectType<['a' | 1, 2, 'c' | 3]>({} as ArrayReverse<['c' | 3, 2, 'a' | 1]>);
16+
17+
// Required and rest element
18+
expectType<readonly [...string[], 3, 2, 1]>({} as ArrayReverse<readonly [1, 2, 3, ...string[]]>); // Rest element at the end
19+
expectType<['z', 'y', 'x', ...boolean[], 3, 2, 1]>({} as ArrayReverse<[1, 2, 3, ...boolean[], 'x', 'y', 'z']>); // Rest element in the middle
20+
expectType<readonly [string, ...bigint[]]>({} as ArrayReverse<readonly [...bigint[], string]>); // Rest element at the start
21+
22+
// Required and optional elements
23+
expectType<[3, 2, 1] | [2, 1]>({} as ArrayReverse<[1, 2, 3?]>);
24+
expectType<readonly [3, 2, 1] | readonly [2, 1] | readonly [1]>({} as ArrayReverse<readonly [1, 2?, 3?]>);
25+
expectType<[number, boolean, bigint, number, string] | [boolean, bigint, number, string] | [bigint, number, string] | [number, string]>(
26+
{} as ArrayReverse<[string, number, bigint?, boolean?, number?]>,
27+
);
28+
29+
// Only optional elements
30+
expectType<['a'] | []>({} as ArrayReverse<['a'?]>);
31+
expectType<readonly [{data: string[]}] | readonly []>({} as ArrayReverse<readonly [{data: string[]}?]>);
32+
expectType<[bigint, boolean, string] | [boolean, string] | [string] | []>({} as ArrayReverse<[string?, boolean?, bigint?]>);
33+
34+
// Required, optional, and rest element
35+
expectType<[...string[], 3, 2, 1] | [2, 1]>({} as ArrayReverse<[1, 2, 3?, ...string[]]>);
36+
expectType<readonly [...string[], string, number] | readonly [number]>({} as ArrayReverse<readonly [number, string?, ...string[]]>);
37+
38+
// Optional and rest element
39+
expectType<readonly [...string[], 1] | readonly []>({} as ArrayReverse<readonly [1?, ...string[]]>);
40+
expectType<[...number[], 3, 2, 1] | [2, 1] | [1] | []>({} as ArrayReverse<[1?, 2?, 3?, ...number[]]>);
41+
42+
// Nested tuples
43+
expectType<[6, 5, [3, 4], [1, 2]]>({} as ArrayReverse<[[1, 2], [3, 4], 5, 6]>);
44+
expectType<[Array<{y: string}>, Array<{x: string}>]>({} as ArrayReverse<[Array<{x: string}>, Array<{y: string}>]>);
45+
expectType<[3, [1, 2]] | [[1, 2]] | []>({} as ArrayReverse<[[1, 2]?, 3?]>);
46+
47+
// Unions
48+
expectType<[3, 2, 1] | readonly ['b', 'a']>({} as ArrayReverse<[1, 2, 3] | readonly ['a', 'b']>);
49+
expectType<readonly [number, string] | number[]>({} as ArrayReverse<readonly [string, number] | number[]>);
50+
expectType<readonly [3, 2, 1] | readonly [2, 1] | readonly [1] | ['b', 'a'] | ['a'] | [] | [number, string]>(
51+
{} as ArrayReverse<readonly [1, 2?, 3?] | ['a'?, 'b'?] | [string, number]>,
52+
);
53+
54+
// Labelled tuples
55+
expectType<[number, number]>({} as ArrayReverse<[x: number, y: number]>);
56+
expectType<readonly [number, string] | readonly [string]>({} as ArrayReverse<readonly [x: string, y?: number]>);
57+
expectType<[...number[], string]>({} as ArrayReverse<[x: string, ...rest: number[]]>);
58+
59+
// Non-tuple arrays
60+
expectType<string[]>({} as ArrayReverse<string[]>);
61+
expectType<readonly number[]>({} as ArrayReverse<readonly number[]>);
62+
expectType<ReadonlyArray<number | bigint>>({} as ArrayReverse<ReadonlyArray<number | bigint>>);
63+
expectType<any[]>({} as ArrayReverse<any[]>);
64+
expectType<never[]>({} as ArrayReverse<never[]>);
65+
66+
// Boundary cases
67+
expectType<never>({} as ArrayReverse<never>);
68+
expectType<any>({} as ArrayReverse<any>);

0 commit comments

Comments
 (0)