Skip to content

Commit

Permalink
Add ArraySplice and OmitDeep type (#816)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
Emiyaaaaa and sindresorhus authored Mar 4, 2024
1 parent 3ec8dba commit 0e196aa
Show file tree
Hide file tree
Showing 8 changed files with 493 additions and 28 deletions.
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type {PickIndexSignature} from './source/pick-index-signature';
export type {PartialDeep, PartialDeepOptions} from './source/partial-deep';
export type {RequiredDeep} from './source/required-deep';
export type {PickDeep} from './source/pick-deep';
export type {OmitDeep} from './source/omit-deep';
export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './source/partial-on-undefined-deep';
export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep';
export type {ReadonlyDeep} from './source/readonly-deep';
Expand Down Expand Up @@ -103,6 +104,7 @@ export type {IsUnknown} from './source/is-unknown';
export type {IfUnknown} from './source/if-unknown';
export type {ArrayIndices} from './source/array-indices';
export type {ArrayValues} from './source/array-values';
export type {ArraySplice} from './source/array-splice';
export type {SetFieldType} from './source/set-field-type';
export type {Paths} from './source/paths';
export type {SharedUnionFieldsDeep} from './source/shared-union-fields-deep';
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Click the type names for complete docs.
- [`RequireOneOrNone`](source/require-one-or-none.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more, or none of the given keys.
- [`RequiredDeep`](source/required-deep.d.ts) - Create a deeply required version of another type. Use [`Required<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) if you only need one level deep.
- [`PickDeep`](source/pick-deep.d.ts) - Pick properties from a deeply-nested object. Use [`Pick<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) if you only need one level deep.
- [`OmitDeep`](source/omit-deep.d.ts) - Omit properties from a deeply-nested object. Use [`Omit<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep.
- [`OmitIndexSignature`](source/omit-index-signature.d.ts) - Omit any index signatures from the given object type, leaving only explicitly defined properties.
- [`PickIndexSignature`](source/pick-index-signature.d.ts) - Pick only index signatures from the given object type, leaving out all explicitly defined properties.
- [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if you only need one level deep.
Expand Down Expand Up @@ -177,6 +178,7 @@ Click the type names for complete docs.
- [`IntRange`](source/int-range.d.ts) - Generate a union of numbers.
- [`ArrayIndices`](source/array-indices.d.ts) - Provides valid indices for a constant array or tuple.
- [`ArrayValues`](source/array-values.d.ts) - Provides all values for a constant array or tuple.
- [`ArraySplice`](source/array-splice.d.ts) - Creates a new array type by adding or removing elements at a specified index range in the original array.
- [`SetFieldType`](source/set-field-type.d.ts) - Create a type that changes the type of the given keys.
- [`Paths`](source/paths.d.ts) - Generate a union of all possible paths to properties in the given object.
- [`SharedUnionFieldsDeep`](source/shared-union-fields-deep.d.ts) - Create a type with shared fields from a union of object types, deeply traversing nested structures.
Expand Down
95 changes: 95 additions & 0 deletions source/array-splice.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type {BuildTuple, Subtract, StaticPartOfArray, VariablePartOfArray, GTE} from './internal';
import type {UnknownArray} from './unknown-array';

/**
The implementation of `SplitArrayByIndex` for fixed length arrays.
*/
type SplitFixedArrayByIndex<T extends UnknownArray, SplitIndex extends number> =
SplitIndex extends 0
? [[], T]
: T extends readonly [...BuildTuple<SplitIndex>, ...infer V]
? T extends readonly [...infer U, ...V]
? [U, V]
: [never, never]
: [never, never];

/**
The implementation of `SplitArrayByIndex` for variable length arrays.
*/
type SplitVariableArrayByIndex<T extends UnknownArray,
SplitIndex extends number,
T1 = Subtract<SplitIndex, StaticPartOfArray<T>['length']>,
T2 = T1 extends number ? BuildTuple<T1, VariablePartOfArray<T>[number]> : [],
> =
SplitIndex extends 0
? [[], T]
: GTE<StaticPartOfArray<T>['length'], SplitIndex> extends true
? [
SplitFixedArrayByIndex<StaticPartOfArray<T>, SplitIndex>[0],
[
...SplitFixedArrayByIndex<StaticPartOfArray<T>, SplitIndex>[1],
...VariablePartOfArray<T>,
],
]
: [
[
...StaticPartOfArray<T>,
...(T2 extends UnknownArray ? T2 : []),
],
VariablePartOfArray<T>,
];

/**
Split the given array `T` by the given `SplitIndex`.
@example
```
type A = SplitArrayByIndex<[1, 2, 3, 4], 2>;
// type A = [[1, 2], [3, 4]];
type B = SplitArrayByIndex<[1, 2, 3, 4], 0>;
// type B = [[], [1, 2, 3, 4]];
```
*/
type SplitArrayByIndex<T extends UnknownArray, SplitIndex extends number> =
SplitIndex extends 0
? [[], T]
: number extends T['length']
? SplitVariableArrayByIndex<T, SplitIndex>
: SplitFixedArrayByIndex<T, SplitIndex>;

/**
Creates a new array type by adding or removing elements at a specified index range in the original array.
Use-case: Replace or insert items in an array type.
Like [`Array#splice()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) but for types.
@example
```
type SomeMonths0 = ['January', 'April', 'June'];
type Mouths0 = ArraySplice<SomeMonths0, 1, 0, ['Feb', 'March']>;
//=> type Mouths0 = ['January', 'Feb', 'March', 'April', 'June'];
type SomeMonths1 = ['January', 'April', 'June'];
type Mouths1 = ArraySplice<SomeMonths1, 1, 1>;
//=> type Mouths1 = ['January', 'June'];
type SomeMonths2 = ['January', 'Foo', 'April'];
type Mouths2 = ArraySplice<SomeMonths2, 1, 1, ['Feb', 'March']>;
//=> type Mouths2 = ['January', 'Feb', 'March', 'April'];
```
@category Array
*/
export type ArraySplice<
T extends UnknownArray,
Start extends number,
DeleteCount extends number,
Items extends UnknownArray = [],
> =
SplitArrayByIndex<T, Start> extends [infer U extends UnknownArray, infer V extends UnknownArray]
? SplitArrayByIndex<V, DeleteCount> extends [infer _Deleted extends UnknownArray, infer X extends UnknownArray]
? [...U, ...Items, ...X]
: never // Should never happen
: never; // Should never happen
100 changes: 100 additions & 0 deletions source/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {IsLiteral} from './is-literal';
import type {UnknownRecord} from './unknown-record';
import type {IsNever} from './is-never';
import type {UnknownArray} from './unknown-array';
import type {IsEqual} from './is-equal';

// TODO: Remove for v5.
export type {UnknownRecord} from './unknown-record';
Expand Down Expand Up @@ -477,3 +478,102 @@ type InternalIsUnion<T, U = T> =
? boolean extends Result ? true
: Result
: never; // Should never happen

/**
Set the given array to readonly if `IsReadonly` is `true`, otherwise set the given array to normal, then return the result.
@example
```
type ReadonlyArray = readonly string[];
type NormalArray = string[];
type ReadonlyResult = SetArrayAccess<NormalArray, true>;
//=> readonly string[]
type NormalResult = SetArrayAccess<ReadonlyArray, false>;
//=> string[]
```
*/
export type SetArrayAccess<T extends UnknownArray, IsReadonly extends boolean> =
T extends readonly [...infer U] ?
IsReadonly extends true
? readonly [...U]
: [...U]
: T;

/**
Returns whether the given array `T` is readonly.
*/
export type IsArrayReadonly<T extends UnknownArray> = T extends unknown[] ? false : true;

/**
Returns the result of `A >= B`.
@example
```
type A = GTE<15, 10>;
//=> true
type B = GTE<10, 15>;
//=> false
type C = GTE<10, 10>;
//=> true
```
*/
export type GTE<A extends number, B extends number> =
BuildTuple<A> extends [...infer _, ...BuildTuple<B>]
? true
: false;

/**
Returns the result of `A > B`
@example
```
type A = GT<15, 10>;
//=> true
type B = GT<10, 15>;
//=> false
*/
export type GT<A extends number, B extends number> =
IsEqual<A, B> extends true
? false
: GTE<A, B>;

/**
Get the exact version of the given `Key` in the given object `T`.
Use-case: You known that a number key (e.g. 10) is in an object, but you don't know how it is defined in the object, as a string or as a number (e.g. 10 or '10'). You can use this type to get the exact version of the key. See the example.
@example
```
type Object = {
0: number;
'1': string;
};
type Key1 = ExactKey<Object, '0'>;
//=> 0
type Key2 = ExactKey<Object, 0>;
//=> 0
type Key3 = ExactKey<Object, '1'>;
//=> '1'
type Key4 = ExactKey<Object, 1>;
//=> '1'
```
@category Object
*/
export type ExactKey<T extends object, Key extends PropertyKey> =
Key extends keyof T
? Key
: ToString<Key> extends keyof T
? ToString<Key>
: Key extends `${infer NumberKey extends number}`
? NumberKey extends keyof T
? NumberKey
: never
: never;
136 changes: 136 additions & 0 deletions source/omit-deep.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type {ArraySplice} from './array-splice';
import type {ExactKey, IsArrayReadonly, NonRecursiveType, SetArrayAccess, ToString} from './internal';
import type {IsEqual} from './is-equal';
import type {SimplifyDeep} from './merge-deep';
import type {Paths} from './paths';
import type {SharedUnionFieldsDeep} from './shared-union-fields-deep';
import type {UnknownArray} from './unknown-array';

/**
Omit properties from a deeply-nested object.
It supports recursing into arrays.
It supports removing specific items from an array, replacing each removed item with unknown at the specified index.
Use-case: Remove unneeded parts of complex objects.
Use [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep.
@example
```
import type {OmitDeep} from 'type-fest';
type Info = {
userInfo: {
name: string;
uselessInfo: {
foo: string;
};
};
};
type UsefulInfo = OmitDeep<Info, 'userInfo.uselessInfo'>;
// type UsefulInfo = {
// userInfo: {
// name: string;
// };
// Supports array
type A = OmitDeep<[1, 'foo', 2], 1>;
// type A = [1, unknown, 2];
// Supports recursing into array
type Info1 = {
address: [
{
street: string
},
{
street2: string,
foo: string
};
];
}
type AddressInfo = OmitDeep<Info1, 'address.1.foo'>;
// type AddressInfo = {
// address: [
// {
// street: string;
// },
// {
// street2: string;
// };
// ];
// };
```
@category Object
@category Array
*/
export type OmitDeep<T, PathUnion extends Paths<T>> =
SimplifyDeep<
SharedUnionFieldsDeep<
{[P in PathUnion]: OmitDeepWithOnePath<T, P>}[PathUnion]
>
>;

/**
Omit one path from the given object/array.
*/
type OmitDeepWithOnePath<T, Path extends string | number> =
T extends NonRecursiveType
? T
: T extends UnknownArray ? SetArrayAccess<OmitDeepArrayWithOnePath<T, Path>, IsArrayReadonly<T>>
: T extends object ? OmitDeepObjectWithOnePath<T, Path>
: T;

/**
Omit one path from the given object.
*/
type OmitDeepObjectWithOnePath<ObjectT extends object, P extends string | number> =
P extends `${infer RecordKeyInPath}.${infer SubPath}`
? {
[Key in keyof ObjectT]:
IsEqual<RecordKeyInPath, ToString<Key>> extends true
? ExactKey<ObjectT, Key> extends infer RealKey
? RealKey extends keyof ObjectT
? OmitDeepWithOnePath<ObjectT[RealKey], SubPath>
: ObjectT[Key]
: ObjectT[Key]
: ObjectT[Key]
}
: ExactKey<ObjectT, P> extends infer Key
? Key extends PropertyKey
? Omit<ObjectT, Key>
: ObjectT
: ObjectT;

/**
Omit one path from from the given array.
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];
```
*/
type OmitDeepArrayWithOnePath<ArrayType extends UnknownArray, P extends string | number> =
// Handle paths that are `${number}.${string}`
P extends `${infer ArrayIndex extends number}.${infer SubPath}`
// 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 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]>
: never;
Loading

0 comments on commit 0e196aa

Please sign in to comment.