Skip to content
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export type {TupleOf} from './source/tuple-of.d.ts';
export type {ExclusifyUnion} from './source/exclusify-union.d.ts';
export type {ArrayReverse} from './source/array-reverse.d.ts';
export type {UnionMember} from './source/union-member.d.ts';
export type {ArrayAt} from './source/array-at.d.ts';

// Template literal types
export type {CamelCase, CamelCaseOptions} from './source/camel-case.d.ts';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ Click the type names for complete docs.
- [`ExcludeRestElement`](source/exclude-rest-element.d.ts) - Create a tuple with the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element removed.
- [`ArrayReverse`](source/array-reverse.d.ts) - Reverse the order of elements in a tuple type.
- [`ArrayLength`](source/array-length.d.ts) - Return the length of an array. Equivalent to `T['length']` where `T` extends any array.
- [`ArrayAt`](source/array-at.d.ts) - Return the element at the given index of the given array.

### Numeric

Expand Down
98 changes: 98 additions & 0 deletions source/array-at.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type {If} from './if.d.ts';
import type {IntRange} from './int-range.d.ts';
import type {NumberAbsolute} from './internal/numeric.d.ts';
import type {IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts';
import type {LessThanOrEqual} from './less-than-or-equal.d.ts';
import type {LessThan} from './less-than.d.ts';
import type {IsNegative} from './numeric.d.ts';
import type {SplitOnRestElement} from './split-on-rest-element.d.ts';
import type {Subtract} from './subtract.d.ts';
import type {Sum} from './sum.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
Return the element at the given index of the given array.

Use-case: Get the element at a specific index of an array.

Like [`Array#at()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at) but for types.

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

// Positive index
type A = ArrayAt<['a', 'b', 'c', 'd'], 1>;
//=> 'b'

// Negative index
type B = ArrayAt<['a', 'b', 'c', 'd'], -4>;
//=> 'a'

// Positive index with optional elements
type C = ArrayAt<['a', 'b', 'c'?, 'd'?], 3>;
//=> 'd' | undefined

// Negative index with optional elements
type D = ArrayAt<['a', 'b', 'c'?, 'd'?], -2>;
//=> 'a' | 'b' | 'c'

// Positive index with rest element and optional elements
type E = ArrayAt<['a', 'b', 'c'?, ...number[]], 3>;
//=> number | undefined

// Negative index with rest element and optional elements
type F = ArrayAt<['a', 'b', 'c'?, ...number[]], -1>;
//=> 'b' | 'c' | number

// Positive index with rest element in middle
type G = ArrayAt<['a', 'b', 'c', ...number[], 'd', 'e'], 4>;
//=> number | 'd' | 'e'

// Negative index with rest element in middle
type H = ArrayAt<['a', 'b', ...number[], 'c', 'd', 'e'], -5>;
//=> 'a' | 'b' | number

// Out-of-bounds positive index
type I = ArrayAt<['a', 'b'], 5>;
//=> undefined

// Out-of-bounds negative index
type J = ArrayAt<['a', 'b'], -3>;
//=> undefined
*/
export type ArrayAt<TArray extends UnknownArray, Index extends number> =
TArray extends unknown // For distributing `Array_`
? Index extends unknown // For distributing `Index`
? number extends Index
? TArray[number] | undefined
: IsNegative<Index> extends true
? ArrayAtNegativeIndex<TArray, Index>
: ArrayAtPositiveIndex<TArray, Index>
: never // Should never happen
: never; // Should never happen

type ArrayAtPositiveIndex<TArray extends UnknownArray, Index extends number> =
SplitOnRestElement<TArray> extends readonly [infer BeforeRest extends UnknownArray, infer Rest extends UnknownArray, infer AfterRest extends UnknownArray]
? LessThan<Index, Required<BeforeRest>['length']> extends true
? BeforeRest[Index]
: Rest[number] | AfterRest[IntRange<0, Sum<Subtract<Index, Required<BeforeRest>['length']>, 1>>]
: never; // Should never happen

type ArrayAtNegativeIndex<TArray extends UnknownArray, Index extends number> =
SplitOnRestElement<TArray> extends readonly [infer BeforeRest extends UnknownArray, infer Rest extends UnknownArray, infer AfterRest extends UnknownArray]
? LessThanOrEqual<NumberAbsolute<Index>, AfterRest['length']> extends true
? AfterRest[Sum<Index, AfterRest['length']>]
: Subtract<Subtract<
BeforeRest['length'],
IntRange<0, Subtract<NumberAbsolute<Index>, AfterRest['length']>>
>, 1> extends infer Indices extends number
?
| Rest[number]
| Required<BeforeRest>[Indices]
| (true extends IsNegative<Indices> ? undefined : never)
| If<IsExactOptionalPropertyTypesEnabled, never, undefined>
: never
: never; // Should never happen

export {};
103 changes: 103 additions & 0 deletions test-d/array-at.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {expectType} from 'tsd';
import type {ArrayAt} from '../index.d.ts';

expectType<ArrayAt<[string], 0>>({} as string);
expectType<ArrayAt<[string], -1>>({} as string);

expectType<ArrayAt<[], 0>>(undefined);
expectType<ArrayAt<[], -1>>(undefined);

expectType<ArrayAt<[number, boolean], 0>>({} as number);
expectType<ArrayAt<[number, boolean], 1>>({} as boolean);
expectType<ArrayAt<[number, boolean], 2>>(undefined);
expectType<ArrayAt<[number, boolean], 99>>(undefined);
expectType<ArrayAt<[number, boolean], -1>>({} as boolean);
expectType<ArrayAt<[number, boolean], -2>>({} as number);
expectType<ArrayAt<[number, boolean], -3>>(undefined);
expectType<ArrayAt<[number, boolean], -99>>(undefined);

expectType<ArrayAt<[number, boolean?], 0>>({} as number);
expectType<ArrayAt<[number, boolean?], 1>>({} as boolean | undefined);
expectType<ArrayAt<[number, boolean?], 2>>(undefined);
expectType<ArrayAt<[number, boolean?], 99>>(undefined);
expectType<ArrayAt<[number, boolean?], -1>>({} as number | boolean);
expectType<ArrayAt<[number, boolean?], -2>>({} as number | undefined);
expectType<ArrayAt<[number, boolean?], -3>>(undefined);
expectType<ArrayAt<[number, boolean?], -99>>(undefined);

type StaticArray = [object, number, boolean?, string?];
expectType<ArrayAt<StaticArray, 0>>({} as object);
expectType<ArrayAt<StaticArray, 1>>({} as number);
expectType<ArrayAt<StaticArray, 2>>({} as boolean | undefined);
expectType<ArrayAt<StaticArray, 3>>({} as string | undefined);
expectType<ArrayAt<StaticArray, -1>>({} as string | boolean | number);
expectType<ArrayAt<StaticArray, -2>>({} as boolean | number | object);
expectType<ArrayAt<StaticArray, -3>>({} as number | object | undefined);
expectType<ArrayAt<StaticArray, -4>>({} as object | undefined);
expectType<ArrayAt<StaticArray, -5>>(undefined);
expectType<ArrayAt<StaticArray, -99>>(undefined);

type LeadingSpreadArray = [...string[], number, boolean];
expectType<ArrayAt<LeadingSpreadArray, 0>>({} as string | number);
expectType<ArrayAt<LeadingSpreadArray, 1>>({} as string | number | boolean);
expectType<ArrayAt<LeadingSpreadArray, 2>>({} as string | number | boolean | undefined);
expectType<ArrayAt<LeadingSpreadArray, 3>>({} as string | number | boolean | undefined);
expectType<ArrayAt<LeadingSpreadArray, -1>>({} as boolean);
expectType<ArrayAt<LeadingSpreadArray, -2>>({} as number);
expectType<ArrayAt<LeadingSpreadArray, -3>>({} as string | undefined);

type TrailingSpreadArray = [number, boolean, ...string[]];
expectType<ArrayAt<TrailingSpreadArray, 0>>({} as number);
expectType<ArrayAt<TrailingSpreadArray, 1>>({} as boolean);
expectType<ArrayAt<TrailingSpreadArray, 2>>({} as string | undefined);
expectType<ArrayAt<TrailingSpreadArray, 3>>({} as string | undefined);
expectType<ArrayAt<TrailingSpreadArray, -1>>({} as string | boolean);
expectType<ArrayAt<TrailingSpreadArray, -2>>({} as boolean | string | number);
expectType<ArrayAt<TrailingSpreadArray, -3>>({} as boolean | string | number | undefined);

type TrailingSpreadArray2 = [object, number, boolean?, ...string[]];
expectType<ArrayAt<TrailingSpreadArray2, 0>>({} as object);
expectType<ArrayAt<TrailingSpreadArray2, 1>>({} as number);
expectType<ArrayAt<TrailingSpreadArray2, 2>>({} as boolean | undefined);
expectType<ArrayAt<TrailingSpreadArray2, 3>>({} as string | undefined);
expectType<ArrayAt<TrailingSpreadArray2, -1>>({} as string | number | boolean);
expectType<ArrayAt<TrailingSpreadArray2, -2>>({} as string | number | boolean | object);
expectType<ArrayAt<TrailingSpreadArray2, -3>>({} as string | number | boolean | object | undefined);

type MiddleSpreadArray = [number, ...string[], boolean];
expectType<ArrayAt<MiddleSpreadArray, 0>>({} as number);
expectType<ArrayAt<MiddleSpreadArray, 1>>({} as string | boolean);
expectType<ArrayAt<MiddleSpreadArray, 2>>({} as string | boolean | undefined);
expectType<ArrayAt<MiddleSpreadArray, 3>>({} as string | boolean | undefined);
expectType<ArrayAt<MiddleSpreadArray, -1>>({} as boolean);
expectType<ArrayAt<MiddleSpreadArray, -2>>({} as string | number);
expectType<ArrayAt<MiddleSpreadArray, -3>>({} as string | number | undefined);

type UnionArray = [string, number] | [boolean, string?];
expectType<ArrayAt<UnionArray, 0>>({} as string | boolean);
expectType<ArrayAt<UnionArray, 1>>({} as number | string | undefined);
expectType<ArrayAt<UnionArray, -1>>({} as number | string | boolean);
expectType<ArrayAt<UnionArray, -2>>({} as undefined | string | boolean);

expectType<ArrayAt<string[], 0>>({} as string | undefined);
expectType<ArrayAt<string[], 1>>({} as string | undefined);
expectType<ArrayAt<string[], 2>>({} as string | undefined);
expectType<ArrayAt<string[], -1>>({} as string | undefined);
expectType<ArrayAt<string[], -2>>({} as string | undefined);
expectType<ArrayAt<string[], -3>>({} as string | undefined);

expectType<ArrayAt<readonly string[], 0>>({} as string | undefined);
expectType<ArrayAt<readonly string[], 1>>({} as string | undefined);
expectType<ArrayAt<readonly string[], 2>>({} as string | undefined);
expectType<ArrayAt<readonly string[], -1>>({} as string | undefined);
expectType<ArrayAt<readonly string[], -2>>({} as string | undefined);
expectType<ArrayAt<readonly string[], -3>>({} as string | undefined);

expectType<ArrayAt<readonly [string, number], 0>>({} as string);
expectType<ArrayAt<readonly [string, number], 1>>({} as number);
expectType<ArrayAt<readonly [string, number], 2>>(undefined);
expectType<ArrayAt<readonly [string, number], -1>>({} as number);

// Test unknown index
expectType<ArrayAt<[1, 2, 3], number>>({} as 1 | 2 | 3 | undefined);
expectType<ArrayAt<string[], number>>({} as string | undefined);
Loading