Skip to content
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

fix(types): union function prop #3119

Merged
merged 5 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 9 additions & 7 deletions packages/runtime-core/src/componentProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type PropConstructor<T = any> =
| { (): T }
| PropMethod<T>

type PropMethod<T, TConstructor = any> = T extends (...args: any) => any // if is function with args
type PropMethod<T, TConstructor = any> = [T] extends [(...args: any) => any] // if is function with args
? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
: never

Expand Down Expand Up @@ -89,17 +89,19 @@ type DefaultKeys<T> = {
: never
}[keyof T]

type InferPropType<T> = T extends null
type InferPropType<T> = [T] extends [null]
? any // null & true would fail to infer
: T extends { type: null | true }
: [T] extends [{ type: null | true }]
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: T extends ObjectConstructor | { type: ObjectConstructor }
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
? Record<string, any>
: T extends BooleanConstructor | { type: BooleanConstructor }
: [T] extends [BooleanConstructor | { type: BooleanConstructor }]
? boolean
: T extends DateConstructor | { type: DateConstructor }
: [T] extends [DateConstructor | { type: DateConstructor }]
? Date
: T extends Prop<infer V, infer D> ? (unknown extends V ? D : V) : T
: [T] extends [Prop<infer V, infer D>]
? (unknown extends V ? D : V)
: T

export type ExtractPropTypes<O> = O extends object
? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &
Expand Down
38 changes: 38 additions & 0 deletions test-dts/defineComponent.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ComponentPublicInstance,
ComponentOptions,
SetupContext,
IsUnion,
h
} from './index'

Expand All @@ -33,6 +34,9 @@ describe('with object props', () => {
hhh: boolean
ggg: 'foo' | 'bar'
ffff: (a: number, b: string) => { a: boolean }
iii?: (() => string) | (() => number)
jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
kkk?: any
validated?: string
date?: Date
}
Expand Down Expand Up @@ -100,6 +104,16 @@ describe('with object props', () => {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
default: (a: number, b: string) => ({ a: a > +b })
},
// union + function with different return types
iii: Function as PropType<(() => string) | (() => number)>,
// union + function with different args & same return type
jjj: {
type: Function as PropType<
((arg1: string) => string) | ((arg1: string, arg2: string) => string)
>,
required: true
},
kkk: null,
validated: {
type: String,
// validator requires explicit annotation
Expand All @@ -126,6 +140,13 @@ describe('with object props', () => {
expectType<ExpectedProps['hhh']>(props.hhh)
expectType<ExpectedProps['ggg']>(props.ggg)
expectType<ExpectedProps['ffff']>(props.ffff)
if (typeof props.iii !== 'function') {
expectType<undefined>(props.iii)
}
expectType<ExpectedProps['iii']>(props.iii)
expectType<IsUnion<typeof props.jjj>>(true)
expectType<ExpectedProps['jjj']>(props.jjj)
expectType<ExpectedProps['kkk']>(props.kkk)
expectType<ExpectedProps['validated']>(props.validated)
expectType<ExpectedProps['date']>(props.date)

Expand Down Expand Up @@ -160,6 +181,13 @@ describe('with object props', () => {
expectType<ExpectedProps['fff']>(props.fff)
expectType<ExpectedProps['hhh']>(props.hhh)
expectType<ExpectedProps['ggg']>(props.ggg)
if (typeof props.iii !== 'function') {
expectType<undefined>(props.iii)
}
expectType<ExpectedProps['iii']>(props.iii)
expectType<IsUnion<typeof props.jjj>>(true)
expectType<ExpectedProps['jjj']>(props.jjj)
expectType<ExpectedProps['kkk']>(props.kkk)

// @ts-expect-error props should be readonly
expectError((props.a = 1))
Expand All @@ -180,6 +208,14 @@ describe('with object props', () => {
expectType<ExpectedProps['fff']>(this.fff)
expectType<ExpectedProps['hhh']>(this.hhh)
expectType<ExpectedProps['ggg']>(this.ggg)
if (typeof this.iii !== 'function') {
expectType<undefined>(this.iii)
}
expectType<ExpectedProps['iii']>(this.iii)
const { jjj } = this
expectType<IsUnion<typeof jjj>>(true)
expectType<ExpectedProps['jjj']>(this.jjj)
expectType<ExpectedProps['kkk']>(this.kkk)

// @ts-expect-error props on `this` should be readonly
expectError((this.a = 1))
Expand Down Expand Up @@ -214,6 +250,7 @@ describe('with object props', () => {
fff={(a, b) => ({ a: a > +b })}
hhh={false}
ggg="foo"
jjj={() => ''}
// should allow class/style as attrs
class="bar"
style={{ color: 'red' }}
Expand All @@ -232,6 +269,7 @@ describe('with object props', () => {
eee={() => ({ a: 'eee' })}
fff={(a, b) => ({ a: a > +b })}
hhh={false}
jjj={() => ''}
/>
)

Expand Down
6 changes: 6 additions & 0 deletions test-dts/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ export function describe(_name: string, _fn: () => void): void
export function expectType<T>(value: T): void
export function expectError<T>(value: T): void
export function expectAssignable<T, T2 extends T = T>(value: T2): void

export type IsUnion<T, U extends T = T> = (T extends any
? (U extends T ? false : true)
: never) extends false
? false
: true