Skip to content

Commit 1e0872d

Browse files
som-smsindresorhus
andauthored
Add IsTuple type (#1024)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent 3d54627 commit 1e0872d

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export type {IsNever} from './source/is-never';
122122
export type {IfNever} from './source/if-never';
123123
export type {IsUnknown} from './source/is-unknown';
124124
export type {IfUnknown} from './source/if-unknown';
125+
export type {IsTuple} from './source/is-tuple';
125126
export type {ArrayIndices} from './source/array-indices';
126127
export type {ArrayValues} from './source/array-values';
127128
export type {ArraySlice} from './source/array-slice';

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
248248
- [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. (Conditional version: [`IfUnknown`](source/if-unknown.d.ts))
249249
- [`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))
250250
- [`IsNull`](source/is-null.d.ts) - Returns a boolean for whether the given type is `null`. (Conditional version: [`IfNull`](source/if-null.d.ts))
251+
- [`IsTuple`](source/is-tuple.d.ts) - Returns a boolean for whether the given array is a tuple.
251252

252253
### JSON
253254

source/is-tuple.d.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type {IfAny} from './if-any';
2+
import type {IfNever} from './if-never';
3+
import type {UnknownArray} from './unknown-array';
4+
5+
/**
6+
@see {@link IsTuple}
7+
*/
8+
export type IsTupleOptions = {
9+
/**
10+
Consider only fixed length arrays as tuples.
11+
12+
- When set to `true` (default), arrays with rest elements (e.g., `[1, ...number[]]`) are _not_ considered as tuples.
13+
- When set to `false`, arrays with at least one non-rest element (e.g., `[1, ...number[]]`) are considered as tuples.
14+
15+
@default true
16+
17+
@example
18+
```ts
19+
import type {IsTuple} from 'type-fest';
20+
21+
type Example1 = IsTuple<[number, ...number[]], {fixedLengthOnly: true}>;
22+
//=> false
23+
24+
type Example2 = IsTuple<[number, ...number[]], {fixedLengthOnly: false}>;
25+
//=> true
26+
```
27+
*/
28+
fixedLengthOnly?: boolean;
29+
};
30+
31+
/**
32+
Returns a boolean for whether the given array is a tuple.
33+
34+
Use-case:
35+
- If you want to make a conditional branch based on the result of whether an array is a tuple or not.
36+
37+
Note: `IsTuple` returns `boolean` when instantiated with a union of tuple and non-tuple (e.g., `IsTuple<[1, 2] | number[]>`).
38+
39+
@example
40+
```ts
41+
import type {IsTuple} from 'type-fest';
42+
43+
type Tuple = IsTuple<[1, 2, 3]>;
44+
//=> true
45+
46+
type NotTuple = IsTuple<number[]>;
47+
//=> false
48+
49+
type TupleWithOptionalItems = IsTuple<[1?, 2?]>;
50+
//=> true
51+
52+
type RestItemsNotAllowed = IsTuple<[1, 2, ...number[]]>;
53+
//=> false
54+
55+
type RestItemsAllowed = IsTuple<[1, 2, ...number[]], {fixedLengthOnly: false}>;
56+
//=> true
57+
```
58+
59+
@see {@link IsTupleOptions}
60+
61+
@category Type Guard
62+
@category Utilities
63+
*/
64+
export type IsTuple<
65+
TArray extends UnknownArray,
66+
Options extends IsTupleOptions = {fixedLengthOnly: true},
67+
> =
68+
IfAny<TArray, boolean, IfNever<TArray, false,
69+
TArray extends unknown // For distributing `TArray`
70+
? number extends TArray['length']
71+
? Options['fixedLengthOnly'] extends false
72+
? IfNever<keyof TArray & `${number}`,
73+
TArray extends readonly [...any, any] ? true : false, // To handle cases where a non-rest element follows a rest element, e.g., `[...number[], number]`
74+
true>
75+
: false
76+
: true
77+
: false
78+
>>;

test-d/is-tuple.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {expectType} from 'tsd';
2+
import type {IsTuple} from '../index';
3+
4+
// Tuples
5+
expectType<IsTuple<[]>>(true);
6+
expectType<IsTuple<[number]>>(true);
7+
expectType<IsTuple<[number, string]>>(true);
8+
expectType<IsTuple<[number, string, ...number[]], {fixedLengthOnly: false}>>(true);
9+
expectType<IsTuple<[number?]>>(true);
10+
expectType<IsTuple<[number?, string?]>>(true);
11+
expectType<IsTuple<[number?, string?, ...number[]], {fixedLengthOnly: false}>>(true);
12+
expectType<IsTuple<[...number[], string, number], {fixedLengthOnly: false}>>(true);
13+
expectType<IsTuple<[never]>>(true);
14+
15+
// Readonly tuples
16+
expectType<IsTuple<readonly []>>(true);
17+
expectType<IsTuple<readonly [number]>>(true);
18+
expectType<IsTuple<readonly [number, string, ...number[]], {fixedLengthOnly: false}>>(true);
19+
expectType<IsTuple<readonly [number?, string?, ...number[]], {fixedLengthOnly: false}>>(true);
20+
expectType<IsTuple<readonly [...number[], string, number], {fixedLengthOnly: false}>>(true);
21+
expectType<IsTuple<readonly [number?]>>(true);
22+
expectType<IsTuple<readonly [never]>>(true);
23+
24+
// Non-tuples
25+
expectType<IsTuple<number[]>>(false);
26+
expectType<IsTuple<readonly number[]>>(false);
27+
expectType<IsTuple<[...number[]]>>(false);
28+
expectType<IsTuple<[number, string, ...number[]]>>(false);
29+
expectType<IsTuple<readonly [number?, string?, ...number[]]>>(false);
30+
expectType<IsTuple<[...number[], string, number]>>(false);
31+
expectType<IsTuple<readonly [...number[], string, number]>>(false);
32+
expectType<IsTuple<never[]>>(false);
33+
expectType<IsTuple<any[]>>(false);
34+
35+
// Boundary types
36+
expectType<IsTuple<any>>({} as boolean);
37+
expectType<IsTuple<any, {fixedLengthOnly: true}>>({} as boolean);
38+
expectType<IsTuple<never>>(false);
39+
40+
// Unions
41+
expectType<IsTuple<[number] | [number, string, boolean]>>(true);
42+
expectType<IsTuple<[number?, string?] | [] | [number, string, ...number[]], {fixedLengthOnly: false}>>(true);
43+
expectType<IsTuple<[number?, string?] | [] | [number, string, ...number[]]>>({} as boolean);
44+
expectType<IsTuple<number[] | string[]>>(false);
45+
expectType<IsTuple<[number, string] | string[]>>({} as boolean);
46+
expectType<IsTuple<[string] | [number] | number[]>>({} as boolean);
47+
expectType<IsTuple<[string, ...number[]] | [number?, string?, ...number[]]>>(false);
48+
expectType<IsTuple<[string, ...number[]] | [number?, string?, ...number[]], {fixedLengthOnly: false}>>(true);
49+
expectType<IsTuple<[] | [number] | readonly [string]>>(true);
50+
51+
// Labeled tuples
52+
expectType<IsTuple<[x: string, y: number]>>(true);
53+
expectType<IsTuple<[first: string, ...rest: number[]]>>(false);
54+
expectType<IsTuple<[first: string, ...rest: number[]], {fixedLengthOnly: false}>>(true);
55+
expectType<IsTuple<readonly [name: string, age?: number]>>(true);
56+
57+
// Mixed optional/required elements
58+
expectType<IsTuple<[string, number?]>>(true);
59+
expectType<IsTuple<readonly [string, number?, ...boolean[]]>>(false);
60+
expectType<IsTuple<readonly [string, number?, ...boolean[]], {fixedLengthOnly: false}>>(true);
61+
62+
// Setting `fixedLengthOnly` to `boolean` falls back to it's default value of `true`
63+
expectType<IsTuple<[number, string, ...number[]], {fixedLengthOnly: boolean}>>(false);
64+
65+
// @ts-expect-error only works with arrays
66+
type T = IsTuple<{}>;

0 commit comments

Comments
 (0)