-
-
Notifications
You must be signed in to change notification settings - Fork 568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SharedUnionFields
type
#994
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import type {NonRecursiveType, IsUnion} from './internal'; | ||
import type {IsNever} from './is-never'; | ||
import type {UnknownArray} from './unknown-array'; | ||
|
||
/** | ||
Create a type with shared fields from a union of object types. | ||
|
||
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 {SharedUnionFields} from 'type-fest'; | ||
|
||
type Cat = { | ||
name: string; | ||
type: 'cat'; | ||
catType: string; | ||
}; | ||
|
||
type Dog = { | ||
name: string; | ||
type: 'dog'; | ||
dogType: string; | ||
}; | ||
|
||
function displayPetInfo(petInfo: Cat | Dog) { | ||
// 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: SharedUnionFields<Cat | Dog>) { | ||
// typeof petInfo => | ||
// { | ||
// name: string; | ||
// type: 'cat' | 'dog'; | ||
// } | ||
|
||
// petInfo type is simple and clear | ||
|
||
console.log('name: ', petInfo.name); | ||
console.log('type: ', petInfo.type); | ||
} | ||
``` | ||
|
||
@see SharedUnionFieldsDeep | ||
|
||
@category Object | ||
@category Union | ||
*/ | ||
export type SharedUnionFields<Union> = | ||
// If `Union` is not a union type, return `Union` directly. | ||
IsUnion<Union> extends false | ||
? Union | ||
// `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> | UnknownArray] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, there's no test case that verifies this conditional A test like the following is needed to verify the above conditional: expectType<Map<string, string> | Set<string>>({} as SharedUnionFields<Map<string, string> | Set<string>>); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, is this the intended behaviour? type T = SharedUnionFields<RegExp | {test: 1} | {test: 2}>;
{ test: 1 | 2 | ((string: string) => boolean); } Feels like one of these behaviours might be better than the existing behaviour in scenarios like these:
@Emiyaaaaa WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is better |
||
? Union | ||
: [Union] extends [object] | ||
// `keyof Union` can extract the same key in union type, if there is no same key, return never. | ||
? keyof Union extends infer Keys | ||
? IsNever<Keys> extends false | ||
? { | ||
[Key in keyof Union]: Union[Key] | ||
} | ||
: {} | ||
: Union | ||
: Union; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import {expectType} from 'tsd'; | ||
import type {SharedUnionFields} from '../index'; | ||
|
||
type TestingType = { | ||
function: (() => void); | ||
record: Record<string, { | ||
propertyA: string; | ||
}>; | ||
object: { | ||
subObject: { | ||
subSubObject: { | ||
propertyA: string; | ||
}; | ||
}; | ||
}; | ||
string: string; | ||
union: 'test1' | 'test2'; | ||
number: number; | ||
boolean: boolean; | ||
date: Date; | ||
regexp: RegExp; | ||
symbol: symbol; | ||
null: null; | ||
undefined: undefined; | ||
optional?: boolean | undefined; | ||
readonly propertyWithKeyword: boolean; | ||
map: Map<string, {propertyA: string; propertyB: string}>; | ||
set: Set<string> ; | ||
objectSet: Set<{propertyA: string; propertyB: string}>; | ||
}; | ||
|
||
declare const normal: SharedUnionFields<TestingType | {string: string; number: number; foo: any}>; | ||
expectType<{string: string; number: number}>(normal); | ||
|
||
declare const normal2: SharedUnionFields<TestingType | {string: string; foo: any}>; | ||
expectType<{string: string}>(normal2); | ||
|
||
declare const unMatched: SharedUnionFields<TestingType | {foo: any}>; | ||
expectType<{}>(unMatched); | ||
|
||
declare const number: SharedUnionFields<TestingType | {number: number; foo: any}>; | ||
expectType<{number: number}>(number); | ||
|
||
declare const string: SharedUnionFields<TestingType | {string: string; foo: any}>; | ||
expectType<{string: string}>(string); | ||
|
||
declare const boolean: SharedUnionFields<TestingType | {boolean: boolean; foo: any}>; | ||
expectType<{boolean: boolean}>(boolean); | ||
|
||
declare const date: SharedUnionFields<TestingType | {date: Date; foo: any}>; | ||
expectType<{date: Date}>(date); | ||
|
||
declare const regexp: SharedUnionFields<TestingType | {regexp: RegExp; foo: any}>; | ||
expectType<{regexp: RegExp}>(regexp); | ||
|
||
declare const symbol: SharedUnionFields<TestingType | {symbol: symbol; foo: any}>; | ||
expectType<{symbol: symbol}>(symbol); | ||
|
||
declare const null_: SharedUnionFields<TestingType | {null: null; foo: any}>; | ||
expectType<{null: null}>(null_); | ||
|
||
declare const undefined_: SharedUnionFields<TestingType | {undefined: undefined; foo: any}>; | ||
expectType<{undefined: undefined}>(undefined_); | ||
|
||
declare const optional: SharedUnionFields<TestingType | {optional: string; foo: any}>; | ||
expectType<{optional?: boolean | string | undefined}>(optional); | ||
|
||
declare const propertyWithKeyword: SharedUnionFields<TestingType | {readonly propertyWithKeyword: string; foo: any}>; | ||
expectType<{readonly propertyWithKeyword: boolean | string}>(propertyWithKeyword); | ||
|
||
declare const map: SharedUnionFields<TestingType | {map: Map<string, {propertyA: string}>; foo: any}>; | ||
expectType<{map: TestingType['map'] | Map<string, {propertyA: string}>}>(map); | ||
|
||
declare const set: SharedUnionFields<TestingType | {set: Set<number>; foo: any}>; | ||
expectType<{set: TestingType['set'] | Set<number>}>(set); | ||
|
||
declare const moreUnion: SharedUnionFields<TestingType | {string: string; number: number; foo: any} | {string: string; bar: any}>; | ||
expectType<{string: string}>(moreUnion); | ||
|
||
declare const union: SharedUnionFields<TestingType | {union: {a: number}}>; | ||
expectType<{union: 'test1' | 'test2' | {a: number}}>(union); | ||
|
||
declare const unionWithOptional: SharedUnionFields<{a?: string; foo: number} | {a: string; bar: string}>; | ||
expectType<{a?: string}>(unionWithOptional); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of this type could be refactored to simply this:
This seems to handle all the scenarios.