Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
60 changes: 55 additions & 5 deletions source/omit-deep.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {SimplifyDeep} from './simplify-deep.d.ts';
import type {Simplify} from './simplify.d.ts';
import type {UnionToTuple} from './union-to-tuple.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
import type {LessThan} from './less-than.d.ts';
import type {IsTuple} from './is-tuple.d.ts';

/**
Omit properties from a deeply-nested object.
Expand Down Expand Up @@ -131,7 +133,10 @@ It replaces the item to `unknown` at the given index.
@example
```
type A = OmitDeepArrayWithOnePath<[10, 20, 30, 40], 2>;
//=> type A = [10, 20, unknown, 40];
//=> [10, 20, unknown, 40];

type B = OmitDeepArrayWithOnePath<[10, 20, 30, 40], 6>;
//=> [10, 20, 30, 40];
```
*/
type OmitDeepArrayWithOnePath<ArrayType extends UnknownArray, P extends string | number> =
Expand All @@ -140,15 +145,60 @@ type OmitDeepArrayWithOnePath<ArrayType extends UnknownArray, P extends string |
// If `ArrayIndex` is equal to `number`
? number extends ArrayIndex
? Array<OmitDeepWithOnePath<NonNullable<ArrayType[number]>, SubPath>>
// If `ArrayIndex` is a number literal
: ArraySplice<ArrayType, ArrayIndex, 1, [OmitDeepWithOnePath<NonNullable<ArrayType[ArrayIndex]>, SubPath>]>
// If `ArrayIndex` is a number literal and out-of-bounds, returns the original type.
: [true, false] extends [IsTuple<ArrayType>, IsValidIndex<ArrayType, ArrayIndex>]
? ArrayType
: ArraySplice<ArrayType, ArrayIndex, 1, [OmitDeepWithOnePath<NonNullable<ArrayType[ArrayIndex]>, SubPath>]>
// If the path is equal to `number`
: P extends `${infer ArrayIndex extends number}`
// If `ArrayIndex` is `number`
? number extends ArrayIndex
? []
// If `ArrayIndex` is a number literal
: ArraySplice<ArrayType, ArrayIndex, 1, [unknown]>
// If `ArrayIndex` is a number literal and out-of-bounds, returns the original type.
: [true, false] extends [IsTuple<ArrayType>, IsValidIndex<ArrayType, ArrayIndex>]
? ArrayType
: ArraySplice<ArrayType, ArrayIndex, 1, [unknown]>
: ArrayType;

/**
`IsValidIndex` returns `true` if `Index` (either as a number or its string representation) is a valid index for the given `Array` or `Tuple`.

If the first argument is a plain `Array`, it accepts `number`.
if it is a `Tuple`, it only accepts specific numeric indices within its bounds.

@example
```
type TupleNumberIndex = IsValidIndex<[0, 1, 2], '2'>;
//=> true
type TupleStringIndex = IsValidIndex<[0, 1, 2], 2>;
//=> true
type TupleNumberOutOfIndex = IsValidIndex<[0, 1, 2], 9>;
//=> false
type TupleStringOutOfIndex = IsValidIndex<[0, 1, 2], '9'>;
//=> false
type TupleNumberOutOfIndexMinus = IsValidIndex<[0, 1, 2], -1>;
//=> false
type TupleNumber = IsValidIndex<[0, 1, 2], number>;
//=> false
type ArrayNumberIndex = IsValidIndex<readonly number[], 0>;
//=> true
type ArrayStringIndex = IsValidIndex<readonly number[], '0'>;
//=> true
type ArrayNumber = IsValidIndex<readonly number[], number>;
//=> true
type NotFixedTuple = IsValidIndex<['0', ...number[]], 0>;
//=> true
```
*/
type IsValidIndex<A extends UnknownArray, Index extends string | number> =
`${Index}` extends `${infer numberString extends number}`
? false extends IsTuple<A>
? true
: number extends numberString
? false
: true extends LessThan<numberString, A['length']>
? LessThan<-1, numberString>
: false
: false;

export {};
7 changes: 7 additions & 0 deletions test-d/omit-deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,10 @@ expectType<{array: Array<{c: string}>}>(arrayWithMultiplePaths);

declare const tupleWithMultiplePaths: OmitDeep<{tuple: [{a: string; b: number; c: string}]}, 'tuple.0.a' | 'tuple.0.b'>;
expectType<{tuple: [{c: string}]}>(tupleWithMultiplePaths);

// Returns the original type unchanged if the specified path does not exist.
type TupleInObject = {obj: {a: [0, 1]; b: {bb: {bbb: 10}}; c: boolean}};
expectType<TupleInObject>({} as OmitDeep<TupleInObject, 'obj.b.bb.zzz' | `obj.a.${2}` | 'c.c'>);

type ObjectInTuple = {obj: {a: [0, 1, {bb: {bbb: 10}}]; c: [0, 1, ['20', '22']]}};
expectType<ObjectInTuple>({} as OmitDeep<ObjectInTuple, 'obj.a.3.3' | 'obj.c.2.9'>);
Comment thread
taiyakihitotsu marked this conversation as resolved.