diff --git a/index.d.ts b/index.d.ts index 3ec2f2284..b60e6e667 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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'; diff --git a/readme.md b/readme.md index b3b65dd71..22912a324 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/source/array-at.d.ts b/source/array-at.d.ts new file mode 100644 index 000000000..33041400f --- /dev/null +++ b/source/array-at.d.ts @@ -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 unknown // For distributing `Array_` + ? Index extends unknown // For distributing `Index` + ? number extends Index + ? TArray[number] | undefined + : IsNegative extends true + ? ArrayAtNegativeIndex + : ArrayAtPositiveIndex + : never // Should never happen + : never; // Should never happen + +type ArrayAtPositiveIndex = + SplitOnRestElement extends readonly [infer BeforeRest extends UnknownArray, infer Rest extends UnknownArray, infer AfterRest extends UnknownArray] + ? LessThan['length']> extends true + ? BeforeRest[Index] + : Rest[number] | AfterRest[IntRange<0, Sum['length']>, 1>>] + : never; // Should never happen + +type ArrayAtNegativeIndex = + SplitOnRestElement extends readonly [infer BeforeRest extends UnknownArray, infer Rest extends UnknownArray, infer AfterRest extends UnknownArray] + ? LessThanOrEqual, AfterRest['length']> extends true + ? AfterRest[Sum] + : Subtract, AfterRest['length']>> + >, 1> extends infer Indices extends number + ? + | Rest[number] + | Required[Indices] + | (true extends IsNegative ? undefined : never) + | If + : never + : never; // Should never happen + +export {}; diff --git a/test-d/array-at.ts b/test-d/array-at.ts new file mode 100644 index 000000000..4dafe69ea --- /dev/null +++ b/test-d/array-at.ts @@ -0,0 +1,103 @@ +import {expectType} from 'tsd'; +import type {ArrayAt} from '../index.d.ts'; + +expectType>({} as string); +expectType>({} as string); + +expectType>(undefined); +expectType>(undefined); + +expectType>({} as number); +expectType>({} as boolean); +expectType>(undefined); +expectType>(undefined); +expectType>({} as boolean); +expectType>({} as number); +expectType>(undefined); +expectType>(undefined); + +expectType>({} as number); +expectType>({} as boolean | undefined); +expectType>(undefined); +expectType>(undefined); +expectType>({} as number | boolean); +expectType>({} as number | undefined); +expectType>(undefined); +expectType>(undefined); + +type StaticArray = [object, number, boolean?, string?]; +expectType>({} as object); +expectType>({} as number); +expectType>({} as boolean | undefined); +expectType>({} as string | undefined); +expectType>({} as string | boolean | number); +expectType>({} as boolean | number | object); +expectType>({} as number | object | undefined); +expectType>({} as object | undefined); +expectType>(undefined); +expectType>(undefined); + +type LeadingSpreadArray = [...string[], number, boolean]; +expectType>({} as string | number); +expectType>({} as string | number | boolean); +expectType>({} as string | number | boolean | undefined); +expectType>({} as string | number | boolean | undefined); +expectType>({} as boolean); +expectType>({} as number); +expectType>({} as string | undefined); + +type TrailingSpreadArray = [number, boolean, ...string[]]; +expectType>({} as number); +expectType>({} as boolean); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | boolean); +expectType>({} as boolean | string | number); +expectType>({} as boolean | string | number | undefined); + +type TrailingSpreadArray2 = [object, number, boolean?, ...string[]]; +expectType>({} as object); +expectType>({} as number); +expectType>({} as boolean | undefined); +expectType>({} as string | undefined); +expectType>({} as string | number | boolean); +expectType>({} as string | number | boolean | object); +expectType>({} as string | number | boolean | object | undefined); + +type MiddleSpreadArray = [number, ...string[], boolean]; +expectType>({} as number); +expectType>({} as string | boolean); +expectType>({} as string | boolean | undefined); +expectType>({} as string | boolean | undefined); +expectType>({} as boolean); +expectType>({} as string | number); +expectType>({} as string | number | undefined); + +type UnionArray = [string, number] | [boolean, string?]; +expectType>({} as string | boolean); +expectType>({} as number | string | undefined); +expectType>({} as number | string | boolean); +expectType>({} as undefined | string | boolean); + +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); + +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); +expectType>({} as string | undefined); + +expectType>({} as string); +expectType>({} as number); +expectType>(undefined); +expectType>({} as number); + +// Test unknown index +expectType>({} as 1 | 2 | 3 | undefined); +expectType>({} as string | undefined);