Skip to content

Commit

Permalink
Add IsTuple type (#1024)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
som-sm and sindresorhus authored Dec 31, 2024
1 parent 3d54627 commit 1e0872d
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export type {IsNever} from './source/is-never';
export type {IfNever} from './source/if-never';
export type {IsUnknown} from './source/is-unknown';
export type {IfUnknown} from './source/if-unknown';
export type {IsTuple} from './source/is-tuple';
export type {ArrayIndices} from './source/array-indices';
export type {ArrayValues} from './source/array-values';
export type {ArraySlice} from './source/array-slice';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
- [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. (Conditional version: [`IfUnknown`](source/if-unknown.d.ts))
- [`IsEmptyObject`](source/empty-object.d.ts) - Returns a boolean for whether the type is strictly equal to an empty plain object, the `{}` value. (Conditional version: [`IfEmptyObject`](source/if-empty-object.d.ts))
- [`IsNull`](source/is-null.d.ts) - Returns a boolean for whether the given type is `null`. (Conditional version: [`IfNull`](source/if-null.d.ts))
- [`IsTuple`](source/is-tuple.d.ts) - Returns a boolean for whether the given array is a tuple.

### JSON

Expand Down
78 changes: 78 additions & 0 deletions source/is-tuple.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type {IfAny} from './if-any';
import type {IfNever} from './if-never';
import type {UnknownArray} from './unknown-array';

/**
@see {@link IsTuple}
*/
export type IsTupleOptions = {
/**
Consider only fixed length arrays as tuples.
- When set to `true` (default), arrays with rest elements (e.g., `[1, ...number[]]`) are _not_ considered as tuples.
- When set to `false`, arrays with at least one non-rest element (e.g., `[1, ...number[]]`) are considered as tuples.
@default true
@example
```ts
import type {IsTuple} from 'type-fest';
type Example1 = IsTuple<[number, ...number[]], {fixedLengthOnly: true}>;
//=> false
type Example2 = IsTuple<[number, ...number[]], {fixedLengthOnly: false}>;
//=> true
```
*/
fixedLengthOnly?: boolean;
};

/**
Returns a boolean for whether the given array is a tuple.
Use-case:
- If you want to make a conditional branch based on the result of whether an array is a tuple or not.
Note: `IsTuple` returns `boolean` when instantiated with a union of tuple and non-tuple (e.g., `IsTuple<[1, 2] | number[]>`).
@example
```ts
import type {IsTuple} from 'type-fest';
type Tuple = IsTuple<[1, 2, 3]>;
//=> true
type NotTuple = IsTuple<number[]>;
//=> false
type TupleWithOptionalItems = IsTuple<[1?, 2?]>;
//=> true
type RestItemsNotAllowed = IsTuple<[1, 2, ...number[]]>;
//=> false
type RestItemsAllowed = IsTuple<[1, 2, ...number[]], {fixedLengthOnly: false}>;
//=> true
```
@see {@link IsTupleOptions}
@category Type Guard
@category Utilities
*/
export type IsTuple<
TArray extends UnknownArray,
Options extends IsTupleOptions = {fixedLengthOnly: true},
> =
IfAny<TArray, boolean, IfNever<TArray, false,
TArray extends unknown // For distributing `TArray`
? number extends TArray['length']
? Options['fixedLengthOnly'] extends false
? IfNever<keyof TArray & `${number}`,
TArray extends readonly [...any, any] ? true : false, // To handle cases where a non-rest element follows a rest element, e.g., `[...number[], number]`
true>
: false
: true
: false
>>;
66 changes: 66 additions & 0 deletions test-d/is-tuple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {expectType} from 'tsd';
import type {IsTuple} from '../index';

// Tuples
expectType<IsTuple<[]>>(true);
expectType<IsTuple<[number]>>(true);
expectType<IsTuple<[number, string]>>(true);
expectType<IsTuple<[number, string, ...number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<[number?]>>(true);
expectType<IsTuple<[number?, string?]>>(true);
expectType<IsTuple<[number?, string?, ...number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<[...number[], string, number], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<[never]>>(true);

// Readonly tuples
expectType<IsTuple<readonly []>>(true);
expectType<IsTuple<readonly [number]>>(true);
expectType<IsTuple<readonly [number, string, ...number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<readonly [number?, string?, ...number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<readonly [...number[], string, number], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<readonly [number?]>>(true);
expectType<IsTuple<readonly [never]>>(true);

// Non-tuples
expectType<IsTuple<number[]>>(false);
expectType<IsTuple<readonly number[]>>(false);
expectType<IsTuple<[...number[]]>>(false);
expectType<IsTuple<[number, string, ...number[]]>>(false);
expectType<IsTuple<readonly [number?, string?, ...number[]]>>(false);
expectType<IsTuple<[...number[], string, number]>>(false);
expectType<IsTuple<readonly [...number[], string, number]>>(false);
expectType<IsTuple<never[]>>(false);
expectType<IsTuple<any[]>>(false);

// Boundary types
expectType<IsTuple<any>>({} as boolean);
expectType<IsTuple<any, {fixedLengthOnly: true}>>({} as boolean);
expectType<IsTuple<never>>(false);

// Unions
expectType<IsTuple<[number] | [number, string, boolean]>>(true);
expectType<IsTuple<[number?, string?] | [] | [number, string, ...number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<[number?, string?] | [] | [number, string, ...number[]]>>({} as boolean);
expectType<IsTuple<number[] | string[]>>(false);
expectType<IsTuple<[number, string] | string[]>>({} as boolean);
expectType<IsTuple<[string] | [number] | number[]>>({} as boolean);
expectType<IsTuple<[string, ...number[]] | [number?, string?, ...number[]]>>(false);
expectType<IsTuple<[string, ...number[]] | [number?, string?, ...number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<[] | [number] | readonly [string]>>(true);

// Labeled tuples
expectType<IsTuple<[x: string, y: number]>>(true);
expectType<IsTuple<[first: string, ...rest: number[]]>>(false);
expectType<IsTuple<[first: string, ...rest: number[]], {fixedLengthOnly: false}>>(true);
expectType<IsTuple<readonly [name: string, age?: number]>>(true);

// Mixed optional/required elements
expectType<IsTuple<[string, number?]>>(true);
expectType<IsTuple<readonly [string, number?, ...boolean[]]>>(false);
expectType<IsTuple<readonly [string, number?, ...boolean[]], {fixedLengthOnly: false}>>(true);

// Setting `fixedLengthOnly` to `boolean` falls back to it's default value of `true`
expectType<IsTuple<[number, string, ...number[]], {fixedLengthOnly: boolean}>>(false);

// @ts-expect-error only works with arrays
type T = IsTuple<{}>;

0 comments on commit 1e0872d

Please sign in to comment.