-
-
Notifications
You must be signed in to change notification settings - Fork 679
Add SplitOnRestElement, ExtractRestElement, ExcludeRestElement types
#1166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
efaf4e3
748d227
5b91505
4f300c8
025098d
59d8598
7fb4958
087e110
cc82002
ed05ff1
7f3f109
cdc27fc
a50d186
adc2483
2a426eb
5c18123
b8d92b4
4be6a52
00857a5
62c6b67
f9665a7
d9482e3
1e218c9
56a7768
f24ded2
2426caf
b32d06e
fe6c7da
03164b7
61c7cdb
2dab2c7
72ad7ca
7ef5d20
b6e9c5e
fdd6e1a
f39bd41
bc4891b
06d8997
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| 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 UnknownArray[] | ||
| ? IsArrayReadonly<Array_> extends true | ||
| ? Readonly<[...Result[0], ...Result[2]]> | ||
| : [...Result[0], ...Result[2]] | ||
| : Result // May be `never` or `any` | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| : never; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| 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. | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| @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] extends infer Result extends UnknownArray | ||
| ? Result extends readonly [] | ||
| ? never | ||
| : Result[number] | ||
| : never; | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import type {IfNotAnyOrNever, IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts'; | ||
| import type {ApplyDefaultOptions} from './internal/object.d.ts'; | ||
| import type {OptionalKeysOf} from './optional-keys-of.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, | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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.preserveOptionalModifier `preserveOptionalModifier`} option to change this behavior. | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| @example | ||
| ```ts | ||
| import type {SplitOnRestElement} from 'type-fest'; | ||
|
|
||
| type T1 = SplitOnRestElement<[number, ...string[], boolean]>; | ||
| //=> [[number], string[], [boolean]] | ||
|
|
||
| type T2 = SplitOnRestElement<[...boolean[], string]>; | ||
| //=> [[], 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<[...number[]]>; | ||
| //=> [[], number[], []] | ||
| ``` | ||
|
|
||
| @see ExtractRestElement, ExcludeRestElement | ||
| @category Array | ||
| */ | ||
| export type SplitOnRestElement<Array_ extends UnknownArray, Options extends SplitOnRestElementOptions = {}> = IfNotAnyOrNever<Array_, | ||
| _SplitOnRestElement<Array_, ApplyDefaultOptions<SplitOnRestElementOptions, DefaultSplitOnRestElementOptions, Options>> | ||
| >; | ||
|
|
||
| /** | ||
| 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 = [], | ||
| > = | ||
| Array_ extends UnknownArray // For distributing `Array_` | ||
| ? 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_, TailAcc] // Add the rest element between the accumulated elements. | ||
| : Array_ extends readonly [(infer First)?, ...infer Rest] | ||
| ? _SplitOnRestElement< | ||
| Rest, Options, | ||
| [ | ||
| ...HeadAcc, | ||
| ...'0' extends OptionalKeysOf<Array_> | ||
| ? Options['preserveOptionalModifier'] extends false | ||
| ? [If<IsExactOptionalPropertyTypesEnabled, First, First | undefined>] // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled. | ||
| : [First?] | ||
| : [First], | ||
| ], | ||
| TailAcc | ||
| > | ||
| : never // Should never happen, since `[(infer First)?, ...infer Rest]` is a top-type for arrays. | ||
| : never; // Should never happen | ||
| 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<[...[], 'last']>>({} as ['last']); | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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?]); | ||
benzaria marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 and Nested | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| expectType<ExcludeRestElement<readonly [...number[], 'done']>>({} as readonly ['done']); | ||
| expectType<ExcludeRestElement<readonly [1, ...string[], 2]>>({} as readonly [1, 2]); | ||
|
|
||
| // Arrays with Arrays Inside | ||
| 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); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import {expectType} from 'tsd'; | ||
| import type {ExtractRestElement} from '../index.d.ts'; | ||
|
|
||
| // Simple Rest element detection | ||
| expectType<ExtractRestElement<['start', ...string[]]>>({} as string); | ||
| expectType<ExtractRestElement<['foo', ...number[], true]>>({} as number); | ||
| expectType<ExtractRestElement<['foo', ...boolean[], 'bar']>>({} as boolean); | ||
| expectType<ExtractRestElement<[...Array<'foo'>, unknown]>>({} as 'foo'); | ||
| expectType<ExtractRestElement<['bar', ...Array<5>]>>({} as 5); | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Leading Rest element | ||
| expectType<ExtractRestElement<[...string[], 1]>>({} as string); | ||
| expectType<ExtractRestElement<[...number[], 'a', 'b']>>({} as number); | ||
| expectType<ExtractRestElement<[...any[], true]>>({} as any); | ||
| expectType<ExtractRestElement<[...[], 'last']>>({} as never); | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| expectType<ExtractRestElement<string[]>>({} as string); | ||
| expectType<ExtractRestElement<readonly number[]>>({} as number); | ||
| expectType<ExtractRestElement<readonly [...boolean[]]>>({} as boolean); | ||
| expectType<ExtractRestElement<[...string[]]>>({} as string); | ||
|
|
||
| // Optional & Mixed Optional | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| expectType<ExtractRestElement<[string?, 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); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| 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 [['a', 'b', 'c'], [], []]); | ||
|
|
||
| // Finite Rest elements (flattened manually or fixed-length rest element) | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| expectType<SplitOnRestElement<[...[1, 2], 3]>>({} as [[1, 2, 3], [], []]); | ||
| expectType<SplitOnRestElement<[...[string?, number?]]>>({} as [[string?, number?], [], []]); | ||
| expectType<SplitOnRestElement<[...[string?, number?]], {preserveOptionalModifier: false}>>({} as [[string, number], [], []]); | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| expectType<SplitOnRestElement<[...[never], 1]>>({} as [[never, 1], [], []]); | ||
| expectType<SplitOnRestElement<[...[], 1]>>({} as [[1], [], []]); | ||
|
|
||
| // Infinite Rest elements (true variadic) | ||
| expectType<SplitOnRestElement<[1, ...number[]]>>({} as [[1], number[], []]); | ||
| 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<[...string[]]>>({} as [[], string[], []]); | ||
| expectType<SplitOnRestElement<[...never[]]>>({} as [[], never[], []]); | ||
| 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]]); | ||
|
|
||
| // Generic Arrays | ||
| expectType<SplitOnRestElement<string[]>>({} as [[], string[], []]); | ||
| expectType<SplitOnRestElement<number[]>>({} as [[], number[], []]); | ||
| expectType<SplitOnRestElement<unknown[]>>({} as [[], unknown[], []]); | ||
| expectType<SplitOnRestElement<any[]>>({} as [[], any[], []]); | ||
|
|
||
| // Readonly | ||
| expectType<SplitOnRestElement<readonly []>>({} as [[], readonly [], []]); | ||
| expectType<SplitOnRestElement<readonly [number]>>({} as [[number], [], []]); | ||
| expectType<SplitOnRestElement<readonly [...number[], 2]>>({} as [[], number[], [2]]); | ||
| expectType<SplitOnRestElement<readonly [1, ...string[], 2]>>({} as [[1], string[], [2]]); | ||
| expectType<SplitOnRestElement<readonly [1, 2, 3]>>({} as [[1, 2, 3], [], []]); | ||
|
||
|
|
||
| // Unions | ||
| expectType<SplitOnRestElement<[...Array<string | number>, boolean]>>({} as [[], Array<string | number>, [boolean]]); | ||
| expectType<SplitOnRestElement<[...Array<{id: string}>, number]>>({} as [[], Array<{id: string}>, [number]]); | ||
| expectType<SplitOnRestElement<[1, ...Array<[string, number]>, 2]>>({} as [[1], Array<[string, number]>, [2]]); | ||
| expectType<SplitOnRestElement<[1, 2] | [3, 4]>>({} as [[1, 2], [], []] | [[3, 4], [], []]); | ||
| expectType<SplitOnRestElement<[1, ...number[]] | ['foo', ...string[]]>>({} as [[1], number[], []] | [['foo'], string[], []]); | ||
|
|
||
| // Exotic Types | ||
benzaria marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| expectType<SplitOnRestElement<[...[], ...[], ...[], 42]>>({} as [[42], [], []]); | ||
| expectType<SplitOnRestElement<[...unknown[], true]>>({} as [[], unknown[], [true]]); | ||
|
|
||
| // Optional/Undefined/Falsy | ||
| expectType<SplitOnRestElement<[0, 1?, 2?, ...never[]]>>({} as [[0, 1?, 2?], never[], []]); | ||
| expectType<SplitOnRestElement<[0, 1?, 2?, ...never[]], {preserveOptionalModifier: false}>>({} as [[0, 1, 2], never[], []]); | ||
| expectType<SplitOnRestElement<[number?, ...string[]]>>({} as [[number?], string[], []]); | ||
| expectType<SplitOnRestElement<[number?, ...string[]], {preserveOptionalModifier: false}>>({} as [[number], string[], []]); | ||
|
|
||
| // Edge: Never / Any | ||
| expectType<SplitOnRestElement<any>>({} as any); | ||
| expectType<SplitOnRestElement<never>>({} as never); | ||
Uh oh!
There was an error while loading. Please reload this page.