fix(types/tsx): optional props from Mixin/Extends are treated as required (#2048)

This commit is contained in:
wonderful-panda 2020-09-16 23:09:35 +09:00 committed by GitHub
parent 7e68ddd354
commit 89e9ab8a2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 46 deletions

View File

@ -13,7 +13,11 @@ import {
AllowedComponentProps, AllowedComponentProps,
ComponentCustomProps ComponentCustomProps
} from './component' } from './component'
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps' import {
ExtractPropTypes,
ComponentPropsOptions,
ExtractDefaultPropTypes
} from './componentProps'
import { EmitsOptions } from './componentEmits' import { EmitsOptions } from './componentEmits'
import { isFunction } from '@vue/shared' import { isFunction } from '@vue/shared'
import { VNodeProps } from './vnode' import { VNodeProps } from './vnode'
@ -37,11 +41,11 @@ export type DefineComponent<
E extends EmitsOptions = Record<string, any>, E extends EmitsOptions = Record<string, any>,
EE extends string = string, EE extends string = string,
PP = PublicProps, PP = PublicProps,
RequiredProps = Readonly<ExtractPropTypes<PropsOrPropOptions>>, Props = Readonly<ExtractPropTypes<PropsOrPropOptions>>,
OptionalProps = Readonly<ExtractPropTypes<PropsOrPropOptions, false>> Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
> = ComponentPublicInstanceConstructor< > = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance< CreateComponentPublicInstance<
OptionalProps, Props,
RawBindings, RawBindings,
D, D,
C, C,
@ -49,12 +53,14 @@ export type DefineComponent<
Mixin, Mixin,
Extends, Extends,
E, E,
PP & OptionalProps PP & Props,
Defaults,
true
> & > &
RequiredProps Props
> & > &
ComponentOptionsBase< ComponentOptionsBase<
RequiredProps, Props,
RawBindings, RawBindings,
D, D,
C, C,
@ -62,7 +68,8 @@ export type DefineComponent<
Mixin, Mixin,
Extends, Extends,
E, E,
EE EE,
Defaults
> & > &
PP PP

View File

@ -42,7 +42,11 @@ import {
WritableComputedOptions, WritableComputedOptions,
toRaw toRaw
} from '@vue/reactivity' } from '@vue/reactivity'
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps' import {
ComponentObjectPropsOptions,
ExtractPropTypes,
ExtractDefaultPropTypes
} from './componentProps'
import { EmitsOptions } from './componentEmits' import { EmitsOptions } from './componentEmits'
import { Directive } from './directives' import { Directive } from './directives'
import { import {
@ -81,7 +85,8 @@ export interface ComponentOptionsBase<
Mixin extends ComponentOptionsMixin, Mixin extends ComponentOptionsMixin,
Extends extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin,
E extends EmitsOptions, E extends EmitsOptions,
EE extends string = string EE extends string = string,
Defaults = {}
> >
extends LegacyOptions<Props, D, C, M, Mixin, Extends>, extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
ComponentInternalOptions, ComponentInternalOptions,
@ -148,6 +153,8 @@ export interface ComponentOptionsBase<
__isFragment?: never __isFragment?: never
__isTeleport?: never __isTeleport?: never
__isSuspense?: never __isSuspense?: never
__defaults?: Defaults
} }
export type ComponentOptionsWithoutProps< export type ComponentOptionsWithoutProps<
@ -159,8 +166,20 @@ export type ComponentOptionsWithoutProps<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string EE extends string = string,
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & { Defaults = {}
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults
> & {
props?: undefined props?: undefined
} & ThisType< } & ThisType<
CreateComponentPublicInstance< CreateComponentPublicInstance<
@ -172,7 +191,9 @@ export type ComponentOptionsWithoutProps<
Mixin, Mixin,
Extends, Extends,
E, E,
Readonly<Props> Readonly<Props>,
Defaults,
false
> >
> >
@ -187,7 +208,18 @@ export type ComponentOptionsWithArrayProps<
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string, EE extends string = string,
Props = Readonly<{ [key in PropNames]?: any }> Props = Readonly<{ [key in PropNames]?: any }>
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & { > = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{}
> & {
props: PropNames[] props: PropNames[]
} & ThisType< } & ThisType<
CreateComponentPublicInstance< CreateComponentPublicInstance<
@ -212,8 +244,20 @@ export type ComponentOptionsWithObjectProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, E extends EmitsOptions = EmitsOptions,
EE extends string = string, EE extends string = string,
Props = Readonly<ExtractPropTypes<PropsOptions>> Props = Readonly<ExtractPropTypes<PropsOptions>>,
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & { Defaults = ExtractDefaultPropTypes<PropsOptions>
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults
> & {
props: PropsOptions & ThisType<void> props: PropsOptions & ThisType<void>
} & ThisType< } & ThisType<
CreateComponentPublicInstance< CreateComponentPublicInstance<
@ -224,7 +268,10 @@ export type ComponentOptionsWithObjectProps<
M, M,
Mixin, Mixin,
Extends, Extends,
E E,
Props,
Defaults,
false
> >
> >
@ -261,6 +308,7 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any, any,
any, any,
any, any,
any,
any any
> >
@ -347,20 +395,22 @@ interface LegacyOptions<
delimiters?: [string, string] delimiters?: [string, string]
} }
export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults'
export type OptionTypesType< export type OptionTypesType<
P = {}, P = {},
B = {}, B = {},
D = {}, D = {},
C extends ComputedOptions = {}, C extends ComputedOptions = {},
M extends MethodOptions = {} M extends MethodOptions = {},
Defaults = {}
> = { > = {
P: P P: P
B: B B: B
D: D D: D
C: C C: C
M: M M: M
Defaults: Defaults
} }
const enum OptionTypes { const enum OptionTypes {

View File

@ -63,18 +63,15 @@ type PropMethod<T, TConstructor = any> = T extends (...args: any) => any // if i
? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor ? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
: never : never
type RequiredKeys<T, MakeDefaultRequired> = { type RequiredKeys<T> = {
[K in keyof T]: T[K] extends [K in keyof T]: T[K] extends { required: true } | { default: any } ? K : never
| { required: true }
| (MakeDefaultRequired extends true ? { default: any } : never)
? K
: never
}[keyof T] }[keyof T]
type OptionalKeys<T, MakeDefaultRequired> = Exclude< type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
keyof T,
RequiredKeys<T, MakeDefaultRequired> type DefaultKeys<T> = {
> [K in keyof T]: T[K] extends { default: any } ? K : never
}[keyof T]
type InferPropType<T> = T extends null type InferPropType<T> = T extends null
? any // null & true would fail to infer ? any // null & true would fail to infer
@ -86,12 +83,9 @@ type InferPropType<T> = T extends null
? boolean ? boolean
: 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< export type ExtractPropTypes<O> = O extends object
O, ? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &
MakeDefaultRequired extends boolean = true { [K in OptionalKeys<O>]?: InferPropType<O[K]> }
> = O extends object
? { [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]> } &
{ [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<O[K]> }
: { [K in string]: any } : { [K in string]: any }
const enum BooleanFlags { const enum BooleanFlags {
@ -99,6 +93,11 @@ const enum BooleanFlags {
shouldCastTrue shouldCastTrue
} }
// extract props which defined with default from prop options
export type ExtractDefaultPropTypes<O> = O extends object
? { [K in DefaultKeys<O>]: InferPropType<O[K]> }
: {}
type NormalizedProp = type NormalizedProp =
| null | null
| (PropOptions & { | (PropOptions & {

View File

@ -77,9 +77,11 @@ type MixinToOptionTypes<T> = T extends ComponentOptionsBase<
infer M, infer M,
infer Mixin, infer Mixin,
infer Extends, infer Extends,
any any,
any,
infer Defaults
> >
? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}> & ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
IntersectionMixin<Mixin> & IntersectionMixin<Mixin> &
IntersectionMixin<Extends> IntersectionMixin<Extends>
: never : never
@ -130,6 +132,8 @@ export type CreateComponentPublicInstance<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
PublicProps = P, PublicProps = P,
Defaults = {},
MakeDefaultsOptional extends boolean = false,
PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>, PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>, PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>, PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
@ -137,7 +141,9 @@ export type CreateComponentPublicInstance<
PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> & PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> &
EnsureNonVoid<C>, EnsureNonVoid<C>,
PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> & PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
EnsureNonVoid<M> EnsureNonVoid<M>,
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
EnsureNonVoid<Defaults>
> = ComponentPublicInstance< > = ComponentPublicInstance<
PublicP, PublicP,
PublicB, PublicB,
@ -146,7 +152,9 @@ export type CreateComponentPublicInstance<
PublicM, PublicM,
E, E,
PublicProps, PublicProps,
ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E> PublicDefaults,
MakeDefaultsOptional,
ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults>
> >
// public properties exposed on the proxy, which is used as the render context // public properties exposed on the proxy, which is used as the render context
@ -159,11 +167,15 @@ export type ComponentPublicInstance<
M extends MethodOptions = {}, M extends MethodOptions = {},
E extends EmitsOptions = {}, E extends EmitsOptions = {},
PublicProps = P, PublicProps = P,
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any> Defaults = {},
MakeDefaultsOptional extends boolean = false,
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>
> = { > = {
$: ComponentInternalInstance $: ComponentInternalInstance
$data: D $data: D
$props: P & PublicProps $props: MakeDefaultsOptional extends true
? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
: P & PublicProps
$attrs: Data $attrs: Data
$refs: Data $refs: Data
$slots: Slots $slots: Slots

View File

@ -41,7 +41,7 @@ export {
} from './apiLifecycle' } from './apiLifecycle'
export { provide, inject } from './apiInject' export { provide, inject } from './apiInject'
export { nextTick } from './scheduler' export { nextTick } from './scheduler'
export { defineComponent, DefineComponent } from './apiDefineComponent' export { defineComponent } from './apiDefineComponent'
export { defineAsyncComponent } from './apiAsyncComponent' export { defineAsyncComponent } from './apiAsyncComponent'
// Advanced API ---------------------------------------------------------------- // Advanced API ----------------------------------------------------------------
@ -166,6 +166,7 @@ export {
ComponentCustomProps, ComponentCustomProps,
AllowedComponentProps AllowedComponentProps
} from './component' } from './component'
export { DefineComponent } from './apiDefineComponent'
export { export {
ComponentOptions, ComponentOptions,
ComponentOptionsMixin, ComponentOptionsMixin,
@ -198,7 +199,8 @@ export {
PropType, PropType,
ComponentPropsOptions, ComponentPropsOptions,
ComponentObjectPropsOptions, ComponentObjectPropsOptions,
ExtractPropTypes ExtractPropTypes,
ExtractDefaultPropTypes
} from './componentProps' } from './componentProps'
export { export {
Directive, Directive,

View File

@ -597,7 +597,11 @@ describe('extends with mixins', () => {
type: String, type: String,
default: 'mP1' default: 'mP1'
}, },
mP2: Boolean mP2: Boolean,
mP3: {
type: Boolean,
required: true
}
}, },
data() { data() {
return { return {
@ -611,6 +615,10 @@ describe('extends with mixins', () => {
p2: { p2: {
type: Number, type: Number,
default: 2 default: 2
},
p3: {
type: Boolean,
required: true
} }
}, },
data() { data() {
@ -663,11 +671,20 @@ describe('extends with mixins', () => {
}) })
// Test TSX // Test TSX
expectType<JSX.Element>(<MyComponent mP1="p1" mP2 p1 p2={1} z={'z'} />) expectType<JSX.Element>(<MyComponent mP1="p1" mP2 mP3 p1 p2={1} p3 z={'z'} />)
// mP1, mP2, p1, and p2 have default value. these are not required
expectType<JSX.Element>(<MyComponent mP3 p3 z={'z'} />)
// missing required props // missing required props
// @ts-expect-error // @ts-expect-error
expectError(<MyComponent />) expectError(<MyComponent mP3 p3 /* z='z' */ />)
// missing required props from mixin
// @ts-expect-error
expectError(<MyComponent /* mP3 */ p3 z="z" />)
// missing required props from extends
// @ts-expect-error
expectError(<MyComponent mP3 /* p3 */ z="z" />)
// wrong prop types // wrong prop types
// @ts-expect-error // @ts-expect-error