Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
efaf4e3
Add: `SplitOnSpread`, `ExtractSpread`, `ExcludeSpread`
benzaria Jun 5, 2025
748d227
`Every`: Fix behavior when the target type is `never` (#1163)
som-sm Jun 4, 2025
5b91505
Add `AllExtend` type (#1164)
som-sm Jun 5, 2025
4f300c8
Recommend using AI to review types
sindresorhus Jun 5, 2025
025098d
Add: `DeconstructSpreadArray`, Deprecate `_SplitOnSpread`, Split `Exc…
benzaria Jun 6, 2025
59d8598
Remove: todo comment
benzaria Jun 6, 2025
7fb4958
Merge branch 'main' into SplitOnSpread
benzaria Jun 6, 2025
087e110
Fix: typos in source/extract-spread.d.ts
benzaria Jun 6, 2025
cc82002
doc: adding Options JsDoc and Enhance JsDoc for `SplitOnSpread` and `…
benzaria Jun 9, 2025
ed05ff1
doc: fix JsDoc for `IsExactOptionalPropertyTypesEnabled` type
benzaria Jun 9, 2025
7f3f109
Merge branch 'main' into SplitOnSpread
benzaria Jun 9, 2025
cdc27fc
doc: sanitize JsDoc
benzaria Jun 9, 2025
a50d186
doc: sanitize JsDoc
benzaria Jun 9, 2025
adc2483
refactor: rename `SplitOnSpread` with `SplitOnRestElement` and relate…
benzaria Jun 10, 2025
2a426eb
doc: refactor left JsDoc & readme description
benzaria Jun 10, 2025
5c18123
doc: ellaborate `keepOptionals` description
som-sm Jun 11, 2025
b8d92b4
Merge branch 'main' into SplitOnSpread
benzaria Jun 11, 2025
4be6a52
feat: rename `keepOptionals` -> `preserveOptionalModifier`, change `S…
benzaria Jun 11, 2025
00857a5
fix: tests for `readonly []`
benzaria Jun 11, 2025
62c6b67
Merge branch 'main' into SplitOnSpread
benzaria Jun 11, 2025
f9665a7
doc: minimize the documentation sentence
benzaria Jun 12, 2025
d9482e3
refactor: move types to coresponding category
benzaria Jun 12, 2025
1e218c9
doc: fix example on `SplitOnRestElement`
benzaria Jun 12, 2025
56a7768
Merge branch 'main' into SplitOnSpread
benzaria Jun 14, 2025
f24ded2
refactor: removed unnecessary test & adjust comments
benzaria Jun 15, 2025
2426caf
feat: add `preserveReadonly` option
benzaria Jun 16, 2025
b32d06e
fix: test errors
benzaria Jun 16, 2025
fe6c7da
refactor: remove `preserveReadonly` option and update `SplitOnRestEle…
benzaria Jun 17, 2025
03164b7
doc: fix example on `SplitOnRestElement`
benzaria Jun 17, 2025
61c7cdb
Update split-on-rest-element.d.ts
sindresorhus Jun 17, 2025
2dab2c7
Merge branch 'main' into SplitOnSpread
benzaria Jun 18, 2025
72ad7ca
Merge branch 'main' into SplitOnSpread
benzaria Sep 23, 2025
7ef5d20
Merge branch 'main' into SplitOnSpread
benzaria Sep 27, 2025
b6e9c5e
minimal changes
benzaria Sep 30, 2025
fdd6e1a
Merge branch 'main' into SplitOnSpread
benzaria Oct 9, 2025
f39bd41
Update split-on-rest-element.d.ts
sindresorhus Oct 10, 2025
bc4891b
`ExcludeRestElement`: remove comment
benzaria Oct 10, 2025
06d8997
Update source/split-on-rest-element.d.ts
sindresorhus Oct 11, 2025
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
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ export type {WritableKeysOf} from './source/writable-keys-of.d.ts';
export type {IsWritableKeyOf} from './source/is-writable-key-of.d.ts';
export type {HasWritableKeys} from './source/has-writable-keys.d.ts';
export type {Spread} from './source/spread.d.ts';
export type {SplitOnRestElement} from './source/split-on-rest-element.d.ts';
export type {ExtractRestElement} from './source/extract-rest-element.d.ts';
export type {ExcludeRestElement} from './source/exclude-rest-element.d.ts';
export type {IsInteger} from './source/is-integer.d.ts';
export type {IsFloat} from './source/is-float.d.ts';
export type {TupleToObject} from './source/tuple-to-object.d.ts';
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ Click the type names for complete docs.
- [`UnionToTuple`](source/union-to-tuple.d.ts) - Convert a union type into an unordered tuple type of its elements.
- [`TupleToObject`](source/tuple-to-object.d.ts) - Transforms a tuple into an object, mapping each tuple index to its corresponding type as a key-value pair.
- [`TupleOf`](source/tuple-of.d.ts) - Creates a tuple type of the specified length with elements of the specified type.
- [`SplitOnRestElement`](source/split-on-rest-element.d.ts) - Splits an array into three parts, where the first contains all elements before the rest element, the second is the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element itself, and the third contains all elements after the rest element.
- [`ExtractRestElement`](source/extract-rest-element.d.ts) - Extracts the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element type from an array.
- [`ExcludeRestElement`](source/exclude-rest-element.d.ts) - Creates a tuple with the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element removed.

### Numeric

Expand Down
37 changes: 37 additions & 0 deletions source/exclude-rest-element.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type {SplitOnRestElement} from './split-on-rest-element.d.ts';
import type {IsArrayReadonly} from './internal/array.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
Creates a tuple with the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element removed.

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

type T1 = ExcludeRestElement<[number, ...string[], string, 'foo']>;
//=> [number, string, 'foo']

type T2 = ExcludeRestElement<[...boolean[], string]>;
//=> [string]

type T3 = ExcludeRestElement<[...'foo'[], true]>;
//=> [true]

type T4 = ExcludeRestElement<[number, string]>;
//=> [number, string]
```

@see ExtractRestElement, SplitOnRestElement
@category Array
*/
export type ExcludeRestElement<Array_ extends UnknownArray> =
SplitOnRestElement<Array_> extends infer Result
? Result extends readonly UnknownArray[]
? IsArrayReadonly<Array_> extends true
? Readonly<[...Result[0], ...Result[2]]>
: [...Result[0], ...Result[2]]
: Result
: never;

export {};
29 changes: 29 additions & 0 deletions source/extract-rest-element.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type {SplitOnRestElement} from './split-on-rest-element.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
Extracts the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element type from an array.

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

type T1 = ExtractRestElement<[number, ...string[], string, 'foo']>;
//=> string

type T2 = ExtractRestElement<[...boolean[], string]>;
//=> boolean

type T3 = ExtractRestElement<[...'foo'[], true]>;
//=> 'foo'

type T4 = ExtractRestElement<[number, string]>;
//=> never
```

@see ExcludeRestElement, SplitOnRestElement
@category Array
*/
export type ExtractRestElement<T extends UnknownArray> = SplitOnRestElement<T>[1][number];

export {};
106 changes: 106 additions & 0 deletions source/split-on-rest-element.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type {IfNotAnyOrNever, IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts';
import type {ApplyDefaultOptions} from './internal/object.d.ts';
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
import type {IsArrayReadonly} from './internal/array.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
import type {If} from './if.d.ts';

/**
{@link SplitOnRestElement} options.
*/
type SplitOnRestElementOptions = {
/**
Whether to preserve the optional modifier (`?`).

- When set to `true`, the optional modifiers are preserved as-is. For example:
`SplitOnRestElement<[number, string?, ...boolean[]], {preserveOptionalModifier: true}>` returns `[[number, string?], boolean[], []]`.

- When set to `false`, optional elements like `T?` are transformed to `T | undefined` or simply `T` depending on the `exactOptionalPropertyTypes` compiler option. For example:
- With `exactOptionalPropertyTypes` enabled: `SplitOnRestElement<[number, string?, ...boolean[]], {preserveOptionalModifier: false}>` returns `[[number, string], boolean[], []]`
- And, with it disabled, the result is: `[[number, string | undefined], boolean[], []]`

@default true
*/
preserveOptionalModifier?: boolean;
};

type DefaultSplitOnRestElementOptions = {
preserveOptionalModifier: true;
};

/**
Splits an array into three parts, where the first contains all elements before the rest element, the second is the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element itself, and the third contains all elements after the rest element.

Note: If any of the parts are missing, then they will be represented as empty arrays. For example, `SplitOnRestElement<[string, number]>` returns `[[string, number], [], []]`, where parts corresponding to the rest element and elements after it are empty.

By default, the optional modifier (`?`) is preserved.
See {@link SplitOnRestElementOptions `SplitOnRestElementOptions`}.

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

type T1 = SplitOnRestElement<[number, ...string[], boolean]>;
//=> [[number], string[], [boolean]]

type T2 = SplitOnRestElement<readonly [...boolean[], string]>;
//=> readonly [[], boolean[], [string]]

type T3 = SplitOnRestElement<[number, string?]>;
//=> [[number, string?], [], []]

type T4 = SplitOnRestElement<[number, string?], {preserveOptionalModifier: false}>;
//=> [[number, string], [], []] or [[number, string | undefined], [], []]

type T5 = SplitOnRestElement<readonly [string?, ...number[]], {preserveOptionalModifier: false}>;
//=> readonly [[string], number[], []] or readonly [[string | undefined], number[], []]
```

@see ExtractRestElement
@see ExcludeRestElement
@category Array
*/
export type SplitOnRestElement<
Array_ extends UnknownArray,
Options extends SplitOnRestElementOptions = {},
> =
Array_ extends unknown // For distributing `Array_`
? IfNotAnyOrNever<Array_, _SplitOnRestElement<
Array_,
ApplyDefaultOptions<SplitOnRestElementOptions, DefaultSplitOnRestElementOptions, Options>
>> extends infer Result extends UnknownArray
? If<IsArrayReadonly<Array_>, Readonly<Result>, Result>
: never // Should never happen
: never; // Should never happen

/**
Deconstructs an array on its rest element and returns the split portions.
*/
export type _SplitOnRestElement<
Array_ extends UnknownArray,
Options extends Required<SplitOnRestElementOptions>,
HeadAcc extends UnknownArray = [],
TailAcc extends UnknownArray = [],
> =
keyof Array_ & `${number}` extends never
// Enters this branch, if `Array_` is empty (e.g., []),
// or `Array_` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`).
? Array_ extends readonly [...infer Rest, infer Last]
? _SplitOnRestElement<Rest, Options, HeadAcc, [Last, ...TailAcc]> // Accumulate elements that are present after the rest element.
: [HeadAcc, Array_ extends readonly [] ? [] : Array_, TailAcc] // Add the rest element between the accumulated elements.
: Array_ extends readonly [(infer First)?, ...infer Rest]
? _SplitOnRestElement<
Rest, Options,
[
...HeadAcc,
...IsOptionalKeyOf<Array_, '0'> extends true
? Options['preserveOptionalModifier'] extends false
? [If<IsExactOptionalPropertyTypesEnabled, First, First | undefined>] // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled.
: [First?]
: [First],
],
TailAcc
> // Accumulate elements that are present before the rest element.
: never; // Should never happen, since `[(infer First)?, ...infer Rest]` is a top-type for arrays.

export {};
53 changes: 53 additions & 0 deletions test-d/exclude-rest-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {expectType} from 'tsd';
import type {ExcludeRestElement} from '../index.d.ts';

// Basic static tuples (No rest element)
expectType<ExcludeRestElement<[]>>({} as []);
expectType<ExcludeRestElement<[1]>>({} as [1]);
expectType<ExcludeRestElement<[1, 2, 3]>>({} as [1, 2, 3]);
expectType<ExcludeRestElement<readonly ['a', 'b']>>({} as readonly ['a', 'b']);

// Leading rest element
expectType<ExcludeRestElement<[...string[], 1]>>({} as [1]);
expectType<ExcludeRestElement<[...number[], 'a', 'b']>>({} as ['a', 'b']);
expectType<ExcludeRestElement<[...any[], true]>>({} as [true]);
expectType<ExcludeRestElement<[...never[], 'end']>>({} as ['end']);
expectType<ExcludeRestElement<[...unknown[], 2, 3]>>({} as [2, 3]);

// Middle rest element
expectType<ExcludeRestElement<['a', ...string[], 'z']>>({} as ['a', 'z']);
expectType<ExcludeRestElement<['x', ...boolean[], true]>>({} as ['x', true]);
expectType<ExcludeRestElement<['x', ...any[], 'y']>>({} as ['x', 'y']);
expectType<ExcludeRestElement<['x', ...readonly number[], 'y']>>({} as ['x', 'y']);

// Trailing rest element
expectType<ExcludeRestElement<[1, 2, ...string[]]>>({} as [1, 2]);
expectType<ExcludeRestElement<['foo', ...Array<'bar'>]>>({} as ['foo']);
expectType<ExcludeRestElement<[number, ...number[]]>>({} as [number]);

// Only rest element
expectType<ExcludeRestElement<string[]>>({} as []);
expectType<ExcludeRestElement<readonly number[]>>({} as readonly []);
expectType<ExcludeRestElement<readonly [...boolean[]]>>({} as readonly []);
expectType<ExcludeRestElement<[...string[]]>>({} as []);

// Optional & mixed optional
expectType<ExcludeRestElement<[string?, boolean?, ...number[]]>>({} as [string?, boolean?]);
expectType<ExcludeRestElement<[number, boolean?, ...number[]]>>({} as [number, boolean?]);
expectType<ExcludeRestElement<[1?, ...string[]]>>({} as [1?]);

// Unions
expectType<ExcludeRestElement<[1, ...string[]] | [2, ...number[]]>>({} as [1] | [2]);
expectType<ExcludeRestElement<[...boolean[], 'end'] | ['start', ...string[]]>>({} as ['end'] | ['start']);

// Readonly
expectType<ExcludeRestElement<readonly [...number[], 'done']>>({} as readonly ['done']);
expectType<ExcludeRestElement<readonly [1, ...string[], 2]>>({} as readonly [1, 2]);

// Nested Arrays
expectType<ExcludeRestElement<[[1, 2], ...number[], [3, 4]]>>({} as [[1, 2], [3, 4]]);
expectType<ExcludeRestElement<[['a'], ...string[], ['z']]>>({} as [['a'], ['z']]);

// Edge: `never` / `any`
expectType<ExcludeRestElement<any>>({} as any);
expectType<ExcludeRestElement<never>>({} as never);
51 changes: 51 additions & 0 deletions test-d/extract-rest-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {expectType} from 'tsd';
import type {ExtractRestElement} from '../index.d.ts';

// Leading rest element
expectType<ExtractRestElement<[...string[], 1]>>({} as string);
expectType<ExtractRestElement<[...number[], 'a', 'b']>>({} as number);
expectType<ExtractRestElement<[...any[], true]>>({} as any);
expectType<ExtractRestElement<[...never[], 'end']>>({} as never);
expectType<ExtractRestElement<[...unknown[], 2, 3]>>({} as unknown);

// Middle rest element
expectType<ExtractRestElement<['a', ...string[], 'z']>>({} as string);
expectType<ExtractRestElement<['x', ...boolean[], true]>>({} as boolean);
expectType<ExtractRestElement<['x', ...any[], 'y']>>({} as any);

// Trailing rest element
expectType<ExtractRestElement<[1, 2, ...string[]]>>({} as string);
expectType<ExtractRestElement<['foo', ...Array<'bar'>]>>({} as 'bar');
expectType<ExtractRestElement<[number, ...number[]]>>({} as number);

// Rest element only
expectType<ExtractRestElement<string[]>>({} as string);
expectType<ExtractRestElement<readonly number[]>>({} as number);
expectType<ExtractRestElement<readonly [...boolean[]]>>({} as boolean);
expectType<ExtractRestElement<[...string[]]>>({} as string);

// Optional
expectType<ExtractRestElement<[string?, boolean?, ...number[]]>>({} as number);
expectType<ExtractRestElement<[number, boolean?, ...number[]]>>({} as number);
expectType<ExtractRestElement<[1?, ...string[]]>>({} as string);

// No rest element
expectType<ExtractRestElement<[1, 2, 3]>>({} as never);
expectType<ExtractRestElement<readonly ['a', 'b']>>({} as never);
expectType<ExtractRestElement<[]>>({} as never);

// Union
expectType<ExtractRestElement<[1, ...string[]] | [2, ...number[]]>>({} as string | number);
expectType<ExtractRestElement<[...boolean[], 'end'] | ['start', ...string[]]>>({} as boolean | string);

// Readonly
expectType<ExtractRestElement<readonly [...number[], 'done']>>({} as number);
expectType<ExtractRestElement<readonly [1, ...string[], 2]>>({} as string);

// Nested arrays
expectType<ExtractRestElement<[[1, 2], ...string[], [3, 4]]>>({} as string);
expectType<ExtractRestElement<[['a'], ...boolean[], ['z']]>>({} as boolean);

// Edge: `never` / `any`
expectType<ExtractRestElement<any>>({} as any);
expectType<ExtractRestElement<never>>({} as never);
55 changes: 55 additions & 0 deletions test-d/split-on-rest-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {expectType} from 'tsd';
import type {SplitOnRestElement} from '../index.d.ts';

// Fixed tuples (No rest element)
expectType<SplitOnRestElement<[]>>({} as [[], [], []]);
expectType<SplitOnRestElement<[1]>>({} as [[1], [], []]);
expectType<SplitOnRestElement<[1, 2, 3]>>({} as [[1, 2, 3], [], []]);
expectType<SplitOnRestElement<readonly ['a', 'b', 'c']>>({} as readonly [['a', 'b', 'c'], [], []]);

// Rest elements (true variadic)
expectType<SplitOnRestElement<[1, ...number[]]>>({} as [[1], number[], []]);
expectType<SplitOnRestElement<[...string[]]>>({} as [[], string[], []]);
expectType<SplitOnRestElement<[...never[]]>>({} as [[], never[], []]);
expectType<SplitOnRestElement<[1, ...number[], 2]>>({} as [[1], number[], [2]]);
expectType<SplitOnRestElement<['a', ...string[], 'b']>>({} as [['a'], string[], ['b']]);
expectType<SplitOnRestElement<[...boolean[], string]>>({} as [[], boolean[], [string]]);
expectType<SplitOnRestElement<[...string[], 'x', 'y']>>({} as [[], string[], ['x', 'y']]);
expectType<SplitOnRestElement<[...never[], 1]>>({} as [[], never[], [1]]);
expectType<SplitOnRestElement<[undefined, ...boolean[], null]>>({} as [[undefined], boolean[], [null]]);
expectType<SplitOnRestElement<[void, ...never[], 1]>>({} as [[void], never[], [1]]);
expectType<SplitOnRestElement<[null, ...any[], null]>>({} as [[null], any[], [null]]);
expectType<SplitOnRestElement<[...Array<{id: string}>, number]>>({} as [[], Array<{id: string}>, [number]]);
expectType<SplitOnRestElement<[1, ...Array<readonly [string, number]>, 2]>>({} as [[1], Array<readonly [string, number]>, [2]]);

// Generic arrays
expectType<SplitOnRestElement<string[]>>({} as [[], string[], []]);
expectType<SplitOnRestElement<number[]>>({} as [[], number[], []]);
expectType<SplitOnRestElement<unknown[]>>({} as [[], unknown[], []]);
expectType<SplitOnRestElement<any[]>>({} as [[], any[], []]);

// Unions
expectType<SplitOnRestElement<[...Array<string | number>, boolean]>>({} as [[], Array<string | number>, [boolean]]);
expectType<SplitOnRestElement<[1, 2] | [3, 4]>>({} as [[1, 2], [], []] | [[3, 4], [], []]);
expectType<SplitOnRestElement<[1, ...number[]] | ['foo', ...string[]]>>({} as [[1], number[], []] | [['foo'], string[], []]);

// Preserve optional
expectType<SplitOnRestElement<[0, 1?, 2?, ...never[]]>>({} as [[0, 1?, 2?], never[], []]);
expectType<SplitOnRestElement<[number?, ...string[]]>>({} as [[number?], string[], []]);
expectType<SplitOnRestElement<[number, boolean?, ...string[]]>>({} as [[number, boolean?], string[], []]);

// Remove optional
expectType<SplitOnRestElement<[0, 1?, 2?, ...never[]], {preserveOptionalModifier: false}>>({} as [[0, 1, 2], never[], []]);
expectType<SplitOnRestElement<[number?, ...string[]], {preserveOptionalModifier: false}>>({} as [[number], string[], []]);
expectType<SplitOnRestElement<[number, boolean?, ...string[]], {preserveOptionalModifier: false}>>({} as [[number, boolean], string[], []]);

// Readonly
expectType<SplitOnRestElement<readonly []>>({} as readonly [[], [], []]);
expectType<SplitOnRestElement<readonly [number] | [string]>>({} as readonly [[number], [], []] | [[string], [], []]);
expectType<SplitOnRestElement<readonly [...number[], 2]>>({} as readonly [[], number[], [2]]);
expectType<SplitOnRestElement<readonly [1, ...string[], 2] | readonly ['foo'?, ...string[]]>>({} as readonly [[1], string[], [2]] | readonly [['foo'?], string[], []]);
expectType<SplitOnRestElement<readonly [1, 2, 3]>>({} as readonly [[1, 2, 3], [], []]);

// Edge: `never` / `any`
expectType<SplitOnRestElement<any>>({} as any);
expectType<SplitOnRestElement<never>>({} as never);