Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export type {ArraySlice} from './source/array-slice.d.ts';
export type {ArraySplice} from './source/array-splice.d.ts';
export type {ArrayTail} from './source/array-tail.d.ts';
export type {ArrayElement} from './source/array-element.d.ts';
export type {ArrayExclude} from './source/array-exclude.d.ts';
export type {SetFieldType, SetFieldTypeOptions} from './source/set-field-type.d.ts';
export type {Paths, PathsOptions} from './source/paths.d.ts';
export type {AllUnionFields} from './source/all-union-fields.d.ts';
Expand Down
71 changes: 71 additions & 0 deletions source/array-exclude.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type {And} from './and.d.ts';
import type {If} from './if.d.ts';
import type {IsArrayReadonly} from './internal/array.d.ts';
import type {IsExactOptionalPropertyTypesEnabled, Not} from './internal/type.d.ts';
import type {IsNever} from './is-never.d.ts';
import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts';
import type {IsTuple} from './is-tuple.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';

/**
Exclude elements of the specified type from an array type.
*/
export type ArrayExclude<TArray extends UnknownArray, TExclude> =
TArray extends UnknownArray // For distributing `TArray`
? If<
IsTuple<TArray, {fixedLengthOnly: false}>,
TupleExclude<TArray, TExclude>,
Array<Exclude<TArray[number], TExclude>>
> extends infer Result
? If<IsArrayReadonly<TArray>, Readonly<Result>, Result>
: never
: never; // Should never happen

type TupleExclude<
TTuple extends UnknownArray,
TExclude,
ForwardAccumulator extends UnknownArray = [],
BackwardAccumulator extends UnknownArray = [],
> =
TTuple extends UnknownArray // For distributing `TArray`
? keyof TTuple & `${number}` extends never
// Enters this branch, if `TArray` is empty (e.g., `[]`),
// or `TArray` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`).
? TTuple extends readonly [...infer Rest, infer Last]
? TupleExclude<
Rest,
TExclude,
ForwardAccumulator,
Exclude<Last, TExclude> extends infer ExcludedLast
? If<IsNever<ExcludedLast>, BackwardAccumulator, [ExcludedLast, ...BackwardAccumulator]>
: never
>
: Exclude<TTuple[number], TExclude> extends infer ExcludedRestElement
? If<
IsNever<ExcludedRestElement>,
[...ForwardAccumulator, ...BackwardAccumulator],
[...ForwardAccumulator, ...ExcludedRestElement[], ...BackwardAccumulator]
>
: never // Should never happen
: TTuple extends readonly [(infer First)?, ...infer Rest]
? TupleExclude<
Rest,
TExclude,
IsOptionalKeyOf<TTuple, '0'> extends infer IsFirstOptional extends boolean
? Exclude<
First | (If<And<IsFirstOptional, Not<IsExactOptionalPropertyTypesEnabled>>, undefined, never>), // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled.
TExclude
> extends infer ExcludedFirst
? If<
IsNever<ExcludedFirst>,
ForwardAccumulator,
[...ForwardAccumulator, ...(If<IsFirstOptional, [ExcludedFirst?], [ExcludedFirst]>)]
>
: never // Should never happen
: never, // Should never happen
BackwardAccumulator
>
: never // Should never happen, since `[(infer First)?, ...infer Rest]` is a top-type for arrays.
: never; // Should never happen

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

// All required elements
expectType<[1, 3, 4]>({} as ArrayExclude<[1, 2, 3, 4], 2>);

// All optional elements
expectType<[1?, 3?, 4?]>({} as ArrayExclude<[1?, 2?, 3?, 4?], 2>);

// Mixed required and optional elements
expectType<[1, 3?, 4?]>({} as ArrayExclude<[1, 2, 3?, 4?], 2>);
expectType<[1, 3?, 4?]>({} as ArrayExclude<[1, 2?, 3?, 4?], 2>);

// Explicit undefined in optional elements
expectType<[1, undefined?, 3?]>({} as ArrayExclude<[1, (2 | undefined)?, 3?], 2>);

// Union elements
expectType<[1, 3, 4 | 5]>({} as ArrayExclude<[1, 2 | 3, 4 | 5], 2>);

// Excluding unions
expectType<[1, 3, 5]>({} as ArrayExclude<[1, 2 | 3, 4 | 5], 2 | 4>);

// Required and rest element
// Trailing rest element
expectType<[2, ...string[]]>({} as ArrayExclude<[1, 2, ...string[]], 1>);
expectType<string[]>({} as ArrayExclude<[1, 2, ...string[]], 1 | 2>);
// Leading rest element
expectType<[...string[], 2]>({} as ArrayExclude<[...string[], 1, 2], 1>);
expectType<string[]>({} as ArrayExclude<[...string[], 1, 2], 1 | 2>);
// Rest element in the middle
expectType<[1, ...string[], 3]>({} as ArrayExclude<[1, 2, ...string[], 3, 4], 2 | 4>);
expectType<string[]>({} as ArrayExclude<[1, 2, ...string[], 3, 4], 1 | 2 | 3 | 4>);

// Optional and rest element
expectType<[1?, ...string[]]>({} as ArrayExclude<[1?, 2?, ...string[]], 2>);
expectType<string[]>({} as ArrayExclude<[1?, 2?, ...string[]], 1 | 2>);

// Required, optional, and rest element
expectType<[1, 4?, ...string[]]>({} as ArrayExclude<[1, 2, 3?, 4?, ...string[]], 2 | 3>);

// Excluding rest element
// Trailing rest element
expectType<[1, 2]>({} as ArrayExclude<[1, 2, ...string[]], string>);
expectType<[1?, 2?]>({} as ArrayExclude<[1?, 2?, ...string[]], string>);
expectType<[1, 2, 3?, 4?]>({} as ArrayExclude<[1, 2, 3?, 4?, ...string[]], string>);
expectType<[1, 2, ...number[]]>({} as ArrayExclude<[1, 2, ...Array<string | number>], string>);
expectType<[1?, 2?, ...number[]]>({} as ArrayExclude<[1?, 2?, ...Array<string | number>], string>);
expectType<[1, 2, 3?, 4?, ...number[]]>({} as ArrayExclude<[1, 2, 3?, 4?, ...Array<string | number>], string>);
// Leading rest element
expectType<[1, 2]>({} as ArrayExclude<[...string[], 1, 2], string>);
expectType<[...number[], 1, 2]>({} as ArrayExclude<[...Array<string | number>, 1, 2], string>);
// Rest element in the middle
expectType<[1, 2, 3]>({} as ArrayExclude<[1, ...string[], 2, 3], string>);
expectType<[1, ...number[], 2, 3]>({} as ArrayExclude<[1, ...Array<string | number>, 2, 3], string>);

// Excluding both rest element and non-rest elements
expectType<[1, 3?, 5?, ...number[]]>({} as ArrayExclude<[1, 2, 3?, (4 | 5)?, ...Array<string | number>], 2 | 4 | string>);

// All elements excluded
expectType<[]>({} as ArrayExclude<[1, 2, 3], 1 | 2 | 3>);
expectType<[]>({} as ArrayExclude<[string, number], string | number>);
expectType<[]>({} as ArrayExclude<[1, 2?, 3?, ...string[]], any>);
expectType<[]>({} as ArrayExclude<[1, 2?, 3?, ...string[]], unknown>);

// No elements excluded
expectType<[1, 2, 3]>({} as ArrayExclude<[1, 2, 3], 4>);
expectType<[1, 2?, 3?, ...string[]]>({} as ArrayExclude<[1, 2?, 3?, ...string[]], never>);

// Non-tuple arrays
expectType<string[]>({} as ArrayExclude<Array<string | number>, number>);
expectType<never[]>({} as ArrayExclude<Array<string | number>, string | number>);
Loading