-
-
Notifications
You must be signed in to change notification settings - Fork 575
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
SharedUnionFieldsDeep
type (#783)
Co-authored-by: Sindre Sorhus <[email protected]>
- Loading branch information
1 parent
00233da
commit 94bb3d3
Showing
6 changed files
with
432 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray} from './internal'; | ||
import type {IsNever} from './is-never'; | ||
import type {UnknownArray} from './unknown-array'; | ||
|
||
/** | ||
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[] | ||
``` | ||
*/ | ||
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. | ||
*/ | ||
type IsArrayReadonly<T extends UnknownArray> = T extends unknown[] ? false : true; | ||
|
||
/** | ||
SharedUnionFieldsDeep options. | ||
@see {@link SharedUnionFieldsDeep} | ||
*/ | ||
export type SharedUnionFieldsDeepOptions = { | ||
/** | ||
When set to true, this option impacts each element within arrays or tuples. If all union values are arrays or tuples, it constructs an array of the shortest possible length, ensuring every element exists in the union array. | ||
@default false | ||
*/ | ||
recurseIntoArrays?: boolean; | ||
}; | ||
|
||
/** | ||
Create a type with shared fields from a union of object types, deeply traversing nested structures. | ||
Use the {@link SharedUnionFieldsDeepOptions `Options`} to specify the behavior for arrays. | ||
Use-cases: | ||
- You want a safe object type where each key exists in the union object. | ||
- You want to focus on the common fields of the union type and don't want to have to care about the other fields. | ||
@example | ||
``` | ||
import type {SharedUnionFieldsDeep} from 'type-fest'; | ||
type Cat = { | ||
info: { | ||
name: string; | ||
type: 'cat'; | ||
catType: string; | ||
}; | ||
}; | ||
type Dog = { | ||
info: { | ||
name: string; | ||
type: 'dog'; | ||
dogType: string; | ||
}; | ||
}; | ||
function displayPetInfo(petInfo: (Cat | Dog)['info']) { | ||
// typeof petInfo => | ||
// { | ||
// name: string; | ||
// type: 'cat'; | ||
// catType: string; // Needn't care about this field, because it's not a common pet info field. | ||
// } | { | ||
// name: string; | ||
// type: 'dog'; | ||
// dogType: string; // Needn't care about this field, because it's not a common pet info field. | ||
// } | ||
// petInfo type is complex and have some needless fields | ||
console.log('name: ', petInfo.name); | ||
console.log('type: ', petInfo.type); | ||
} | ||
function displayPetInfo(petInfo: SharedUnionFieldsDeep<Cat | Dog>['info']) { | ||
// typeof petInfo => | ||
// { | ||
// name: string; | ||
// type: 'cat' | 'dog'; | ||
// } | ||
// petInfo type is simple and clear | ||
console.log('name: ', petInfo.name); | ||
console.log('type: ', petInfo.type); | ||
} | ||
``` | ||
@category Object | ||
@category Union | ||
*/ | ||
export type SharedUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions = {recurseIntoArrays: false}> = | ||
// `Union extends` will convert `Union` | ||
// to a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types). | ||
// But this is not what we want, so we need to wrap `Union` with `[]` to prevent it. | ||
[Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>] | ||
? Union | ||
: [Union] extends [UnknownArray] | ||
? Options['recurseIntoArrays'] extends true | ||
? SetArrayAccess<SharedArrayUnionFieldsDeep<Union, Options>, IsArrayReadonly<Union>> | ||
: Union | ||
: [Union] extends [object] | ||
? SharedObjectUnionFieldsDeep<Union, Options> | ||
: Union; | ||
|
||
/** | ||
Same as `SharedUnionFieldsDeep`, but accepts only `object`s and as inputs. Internal helper for `SharedUnionFieldsDeep`. | ||
*/ | ||
type SharedObjectUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions> = | ||
keyof Union extends infer Keys | ||
? IsNever<Keys> extends false | ||
? { | ||
[Key in keyof Union]: | ||
Union[Key] extends NonRecursiveType | ||
? Union[Key] | ||
: SharedUnionFieldsDeep<Union[Key], Options> | ||
} | ||
: {} | ||
: Union; | ||
|
||
/** | ||
Same as `SharedUnionFieldsDeep`, but accepts only `UnknownArray`s and as inputs. Internal helper for `SharedUnionFieldsDeep`. | ||
*/ | ||
type SharedArrayUnionFieldsDeep<Union extends UnknownArray, Options extends SharedUnionFieldsDeepOptions> = | ||
// Restore the readonly modifier of the array. | ||
SetArrayAccess< | ||
InternalSharedArrayUnionFieldsDeep<Union, Options>, | ||
IsArrayReadonly<Union> | ||
>; | ||
|
||
/** | ||
Internal helper for `SharedArrayUnionFieldsDeep`. Needn't care the `readonly` modifier of arrays. | ||
*/ | ||
type InternalSharedArrayUnionFieldsDeep< | ||
Union extends UnknownArray, | ||
Options extends SharedUnionFieldsDeepOptions, | ||
ResultTuple extends UnknownArray = [], | ||
> = | ||
// We should build a minimum possible length tuple where each element in the tuple exists in the union tuple. | ||
IsNever<TupleLength<Union>> extends true | ||
// Rule 1: If all the arrays in the union have non-fixed lengths, | ||
// like `Array<string> | [number, ...string[]]` | ||
// we should build a tuple that is [the_fixed_parts_of_union, ...the_rest_of_union[]]. | ||
// For example: `InternalSharedArrayUnionFieldsDeep<Array<string> | [number, ...string[]]>` | ||
// => `[string | number, ...string[]]`. | ||
? ResultTuple['length'] extends UnionMax<StaticPartOfArray<Union>['length']> | ||
? [ | ||
// The fixed-length part of the tuple. | ||
...ResultTuple, | ||
// The rest of the union. | ||
// Due to `ResultTuple` is the maximum possible fixed-length part of the tuple, | ||
// so we can use `StaticPartOfArray` to get the rest of the union. | ||
...Array< | ||
SharedUnionFieldsDeep<VariablePartOfArray<Union>[number], Options> | ||
>, | ||
] | ||
// Build the fixed-length tuple recursively. | ||
: InternalSharedArrayUnionFieldsDeep< | ||
Union, Options, | ||
[...ResultTuple, SharedUnionFieldsDeep<Union[ResultTuple['length']], Options>] | ||
> | ||
// Rule 2: If at least one of the arrays in the union have fixed lengths, | ||
// like `Array<string> | [number, string]`, | ||
// we should build a tuple of the smallest possible length to ensure any | ||
// item in the result tuple exists in the union tuple. | ||
// For example: `InternalSharedArrayUnionFieldsDeep<Array<string> | [number, string]>` | ||
// => `[string | number, string]`. | ||
: ResultTuple['length'] extends UnionMin<TupleLength<Union>> | ||
? ResultTuple | ||
// As above, build tuple recursively. | ||
: InternalSharedArrayUnionFieldsDeep< | ||
Union, Options, | ||
[...ResultTuple, SharedUnionFieldsDeep<Union[ResultTuple['length']], Options>] | ||
>; |
Oops, something went wrong.