Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Rename: array-reverse -> reverse, Add: Reverse test, IsLeadingSpread
  • Loading branch information
benzaria committed Jun 4, 2025
commit 4f5d1a44c9ef2ab886294cff8b29ba27873b323e
6 changes: 0 additions & 6 deletions source/array-reverse.d.ts

This file was deleted.

129 changes: 129 additions & 0 deletions source/reverse.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type {IsArrayReadonly, IsLeadingSpread} from './internal/array.d.ts';
import type {ApplyDefaultOptions} from './internal/object.d.ts';
import type {ExtendsStrict} from './internal/type.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
import type {ArrayTail} from './array-tail.d.ts';
import type {IsAny} from './is-any.d.ts';
import type {And} from './and.d.ts';
import type {Or} from './or.d.ts';

/**
@see {@link Reverse}
*/
type ArrayReverseOptions = {
/**
Return a reversed readonly array if the input array is readonly.

@default true

@example
```
import type {Reverse} from 'type-fest';

type Example1 = Reverse<readonly [string, number, boolean], {preserveReadonly: true}>;
//=> readonly [boolean, number, string]

type Example2 = Reverse<[string, number, boolean], {preserveReadonly: true}>;
//=> [boolean, number, string]

type Example3 = Reverse<readonly [string, number, boolean], {preserveReadonly: false}>;
//=> [boolean, number, string]

type Example4 = Reverse<[string, number, boolean], {preserveReadonly: false}>;
//=> [boolean, number, string]
```
*/
preserveReadonly?: boolean;
/**
Return a reversed array with Optional keys as `type | undefined`.

@default false

@example
```
import type {Reverse} from 'type-fest';

type Example1 = Reverse<[string, number, boolean?], {keepOptional: false}>;
//=> [boolean, number, string]

type Example2 = Reverse<[string, number, boolean?], {keepOptional: true}>;
//=> [boolean | undefined, number, string]

type Example3 = Reverse<[string, number, boolean], {keepOptional: false}>;
//=> [boolean, number, string]

type Example4 = Reverse<[string, number, boolean], {keepOptional: true}>;
//=> [boolean, number, string]
```
*/
keepOptionals?: boolean;
};

type DefaultArrayReverseOptions = {
preserveReadonly: true;
keepOptionals: false;
};

/**
Reverse an Array Items.
*/
type _Reverse<Array_ extends UnknownArray, Options extends Required<ArrayReverseOptions>, AHead = Array_[0]> =
IsAny<Array_> extends false
? Array_ extends readonly []
? []
: [
..._Reverse<ArrayTail<Array_>, Options>,
...IsLeadingSpread<Array_> extends true
? AHead[]
: [
Or<
Options['keepOptionals'],
ExtendsStrict<AHead, undefined>
> extends true
? AHead
: Exclude<AHead, undefined>,
],
]
: never;

/**
Creates a new array type by Reversing the order of each element in the original array.

By default, The type Preserve `readonly` modifier, and replace Optional keys with `type | undefined`. See {@link ArrayReverseOptions} options to change this behaviour.

@example
```
type T = Reverse<['a', 'b', 'c']>
//=> ['c', 'b', 'a']

type T = Reverse<readonly [1, 2, 3, 4, 5, 6]>
//=> readonly [6, 5, 4, 3, 2, 1]

type T = Reverse<['a', 'b'?, 'c'?]>
//=> ['c', 'b', 'a']

type T = Reverse<readonly [1, 2, ...number[]]>
//=> readonly [...number[], 2, 1]

declare function reverse<const T extends unknown[]>(array: T): Reverse<T>;
reverse(['a', 'b', 'c', 'd']);
//=> ['d', 'c', 'b', 'a']
```

@author benzaria
@see {@link ArrayReverseOptions}
@category Array
*/
export type Reverse<Array_ extends UnknownArray, Options extends ArrayReverseOptions = {}> =
ApplyDefaultOptions<ArrayReverseOptions, DefaultArrayReverseOptions, Options> extends infer ResolvedOptions extends Required<ArrayReverseOptions>
? Array_ extends UnknownArray // For distributing `Array_`
? _Reverse<Array_, ResolvedOptions> extends infer Result
? And<
ResolvedOptions['preserveReadonly'],
IsArrayReadonly<Array_>
> extends true
? Readonly<Result>
: Result
: never
: never
: never;
56 changes: 56 additions & 0 deletions test-d/internal/is-leading-spread.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {expectType} from 'tsd';
import type {IsLeadingSpread} from '../../source/internal/array.d.ts';

// Fixed Tuples
expectType<IsLeadingSpread<[]>>(false);
expectType<IsLeadingSpread<[1]>>(false);
expectType<IsLeadingSpread<[1, 2, 3]>>(false);
expectType<IsLeadingSpread<readonly ['a', 'b', 'c']>>(false);

// Finite Spread
expectType<IsLeadingSpread<[...[number, number], string]>>(false);
expectType<IsLeadingSpread<[...number[], string[]]>>(true);
expectType<IsLeadingSpread<[...[string?, number?]]>>(false);
expectType<IsLeadingSpread<[...[never], 1]>>(false);
expectType<IsLeadingSpread<[...[], 1]>>(false);

// Spread at the end (not leading)
expectType<IsLeadingSpread<[1, ...number[]]>>(false);
expectType<IsLeadingSpread<['start', ...string[]]>>(false);

// Spread at the middle (not leading)
expectType<IsLeadingSpread<[1, ...number[], 2]>>(false);
expectType<IsLeadingSpread<['start', ...string[], 'end']>>(false);

// Spread at the start (leading)
expectType<IsLeadingSpread<string[]>>(true);
expectType<IsLeadingSpread<number[]>>(true);
expectType<IsLeadingSpread<any[]>>(true);
expectType<IsLeadingSpread<unknown[]>>(true);
expectType<IsLeadingSpread<readonly any[]>>(true);
expectType<IsLeadingSpread<readonly [...number[], 2]>>(true);
expectType<IsLeadingSpread<[...string[], 'end']>>(true);
expectType<IsLeadingSpread<[...string[]]>>(true);
expectType<IsLeadingSpread<[...never[], 1]>>(true);
expectType<IsLeadingSpread<[...Array<[number, number | string]>, unknown]>>(true);

// Edge cases
expectType<IsLeadingSpread<readonly []>>(false);
expectType<IsLeadingSpread<readonly [number]>>(false);
expectType<IsLeadingSpread<[...[], 1]>>(false);

// Wrapped inferable patterns
type FromFunction<T extends unknown[]> = (...args: T) => void;

declare const f1: FromFunction<[1, 2, 3]>;
declare const f2: FromFunction<number[]>;

type Args1 = Parameters<typeof f1>;
type Args2 = Parameters<typeof f2>;

expectType<IsLeadingSpread<Args1>>(false);
expectType<IsLeadingSpread<Args2>>(true);

// Readonly
expectType<IsLeadingSpread<readonly [1, 2, 3]>>(false);
expectType<IsLeadingSpread<readonly [...number[], 2]>>(true);
90 changes: 90 additions & 0 deletions test-d/reverse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {expectType} from 'tsd';
import type {Reverse} from '../index.d.ts';

declare const never: never;
declare const string: string;
declare const number: number;
declare const symbol: symbol;
declare const boolean: boolean;

// Edge cases
expectType<Reverse<[]>>([]);
expectType<Reverse<any>>(never);
expectType<Reverse<never>>(never);

// Single element
expectType<Reverse<[1]>>([1] as const);
expectType<Reverse<['a']>>(['a'] as const);
expectType<Reverse<[undefined]>>([undefined] as const);
expectType<Reverse<[null]>>([null] as const);
expectType<Reverse<[boolean]>>([boolean] as const);
expectType<Reverse<[true]>>([true] as const);
expectType<Reverse<[false]>>([false] as const);
expectType<Reverse<[symbol]>>([symbol] as const);
expectType<Reverse<[0n]>>([0n] as const);

// Two and three elements
expectType<Reverse<[1, 2]>>([2, 1] as const);
expectType<Reverse<[1, 'a']>>(['a', 1] as const);
expectType<Reverse<[true, false]>>([false, true] as const);
expectType<Reverse<[1, 2, 3]>>([3, 2, 1] as const);
expectType<Reverse<['a', 'b', 'c']>>(['c', 'b', 'a'] as const);
expectType<Reverse<[1, boolean, 'x']>>(['x', boolean, 1] as const);
expectType<Reverse<[number, string, boolean]>>([boolean, string, number] as const);
expectType<Reverse<[1, 2, 3, 4]>>([4, 3, 2, 1] as const);
expectType<Reverse<[0, 1, 2, 3, 4]>>([4, 3, 2, 1, 0] as const);
expectType<Reverse<['x', 1, true]>>([true, 1, 'x'] as const);

// Optional/undefined
expectType<Reverse<[1?, 2?, 3?]>>([3, 2, 1] as const);
expectType<Reverse<[undefined, 1, 2]>>([2, 1, undefined] as const);
expectType<Reverse<[1 | undefined, 2 | undefined]>>([2, 1] as const);
expectType<Reverse<[string, number?]>>([number, string] as const);
expectType<Reverse<[number?, string?]>>([string, number] as const);
expectType<Reverse<[...[]]>>([] as const);
expectType<Reverse<[...['a']]> extends readonly ['a'] ? true : false>(true);
expectType<Reverse<[1, ...[2, 3]]>>([3, 2, 1] as const);
expectType<Reverse<[1, ...[2], 3]>>([3, 2, 1] as const);
expectType<Reverse<[...['x', 'y'], 'z']>>(['z', 'y', 'x'] as const);

// Mixed types
expectType<Reverse<[1, 'a', true, null, undefined]>>([undefined, null, true, 'a', 1] as const);
expectType<Reverse<['start', ...[true, 99], 'end']>>(['end', 99, true, 'start'] as const);
expectType<Reverse<['x', 1 | 2 | 3]>>([number as 1 | 2 | 3, 'x'] as const);
expectType<Reverse<[...[], 1]>>([1] as const);
expectType<Reverse<[1, 2?, 3?]>>([3, 2, 1] as const);
expectType<Reverse<[boolean, ...[1, 2], string]>>([string, 2, 1, boolean] as const);
expectType<Reverse<['a', ...[number, string], 'b']>>(['b', string, number, 'a'] as const);
expectType<Reverse<[...['a'], ...['b'], 'c']>>(['c', 'b', 'a'] as const);
expectType<Reverse<[...never[], 2, 1]>>([1] as const);
expectType<Reverse<readonly [1, 2, 3]>>([3, 2, 1] as const);

// Large tuples, readonly modifiers
expectType<Reverse<readonly [1, 2, 3, 4, 5, 6]>>([6, 5, 4, 3, 2, 1] as const);
expectType<Reverse<readonly ['a', 'b', 'c', 'd']>>(['d', 'c', 'b', 'a'] as const);
expectType<Reverse<readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>>([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] as const);
expectType<Reverse<readonly ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']>>(['h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] as const);
expectType<Reverse<readonly [readonly number[], boolean]>>([boolean, [number] as number[]] as const);
expectType<Reverse<readonly [string[], ...[1, 2], 'z']>>(['z', 2, 1, [string] as string[]] as const);
expectType<Reverse<readonly ['a', 'b', 'c', 'd', 'e']>>(['e', 'd', 'c', 'b', 'a'] as const);
expectType<Reverse<readonly [boolean, boolean, boolean]>>([boolean, boolean, boolean] as const);
expectType<Reverse<readonly ['x', ...[], 'y']>>(['y', 'x'] as const);
expectType<Reverse<readonly [...['1', '2', '3']]> extends readonly ['3', '2', '1'] ? true : false>(true);

// Union
expectType<Reverse<[1, 2] | [3, 4]>>({} as [2, 1] | [4, 3]);
expectType<Reverse<[1, 'a'] | ['b', 2]>>({} as ['a', 1] | [2, 'b']);

// Spread
expectType<Reverse<[true, ...boolean[]]>>({} as [...boolean[], true]);
expectType<Reverse<[1, 2, ...number[]]>>({} as [...number[], 2, 1]);
expectType<Reverse<['a', `on${string}`, 'c']>>(['c', `on${string}`, 'a'] as const);
expectType<Reverse<[1, 'x', ...boolean[]]>>([boolean, 'x', 1] as const);

expectType<Reverse<[...boolean[], true]>>({} as boolean[]);
expectType<Reverse<[1, ...number[], 2]>>({} as [...number[], 1]);
expectType<Reverse<[1, ...number[], 'end']>>({} as [...Array<(number | 'end')>, 1]);

// In Use
declare function reverse<const T extends unknown[]>(array: T): Reverse<T>;
expectType<['d', 'c', 'b', 'a']>(reverse(['a', 'b', 'c', 'd']));