diff --git a/source/omit-deep.d.ts b/source/omit-deep.d.ts index d3fd115c1..21eeead3d 100644 --- a/source/omit-deep.d.ts +++ b/source/omit-deep.d.ts @@ -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. @@ -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 = @@ -140,15 +145,60 @@ type OmitDeepArrayWithOnePath, SubPath>> - // If `ArrayIndex` is a number literal - : ArraySplice, SubPath>]> + // If `ArrayIndex` is a number literal and out-of-bounds, returns the original type. + : [true, false] extends [IsTuple, IsValidIndex] + ? ArrayType + : ArraySplice, 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 + // If `ArrayIndex` is a number literal and out-of-bounds, returns the original type. + : [true, false] extends [IsTuple, IsValidIndex] + ? ArrayType + : ArraySplice : 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; +//=> true +type ArrayStringIndex = IsValidIndex; +//=> true +type ArrayNumber = IsValidIndex; +//=> true +type NotFixedTuple = IsValidIndex<['0', ...number[]], 0>; +//=> true +``` +*/ +type IsValidIndex = + `${Index}` extends `${infer numberString extends number}` + ? false extends IsTuple + ? true + : number extends numberString + ? false + : true extends LessThan + ? LessThan<-1, numberString> + : false + : false; + export {}; diff --git a/test-d/omit-deep.ts b/test-d/omit-deep.ts index 3467943df..f4b49d76b 100644 --- a/test-d/omit-deep.ts +++ b/test-d/omit-deep.ts @@ -141,3 +141,19 @@ 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({} as OmitDeep); + +type ObjectInTuple = {obj: {a: [0, 1, {bb: {bbb: 10}}]; c: [0, 1, ['20', '22']]}}; +expectType({} as OmitDeep); + +declare const tupleNegativeIndex: OmitDeep<{tuple: ['a', 'b']}, 'tuple.-1'>; +expectType<{tuple: ['a', 'b']}>(tupleNegativeIndex); + +declare const tupleNegativeNestedIndex: OmitDeep<{tuple: [{x: 1}]}, 'tuple.-1.x'>; +expectType<{tuple: [{x: 1}]}>(tupleNegativeNestedIndex); + +declare const tupleMixedValidAndNegativeIndex: OmitDeep<{tuple: [{x: 1; y: 2}]}, 'tuple.0.x' | 'tuple.-1.y'>; +expectType<{tuple: [{y: 2}]}>(tupleMixedValidAndNegativeIndex);