From 0ab27f24b99aa97fa9158c44cf503b90f6f3f9fa Mon Sep 17 00:00:00 2001 From: rudy-xhd Date: Fri, 17 Feb 2023 00:29:40 +0800 Subject: [PATCH] fix(types): support inferring `inject` --- types/common.d.ts | 2 +- types/options.d.ts | 28 +++- types/test/v3/define-component-test.tsx | 207 ++++++++++++++++++++++++ types/v3-component-options.d.ts | 51 ++++-- types/v3-component-public-instance.d.ts | 26 ++- types/v3-define-component.d.ts | 46 ++++-- 6 files changed, 320 insertions(+), 40 deletions(-) diff --git a/types/common.d.ts b/types/common.d.ts index 01d1efbfc7a..c6439adf407 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -5,7 +5,7 @@ export type UnionToIntersection = ( ) extends (k: infer I) => void ? I : never - + // Conditional returns can enforce identical types. // See here: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650 // prettier-ignore diff --git a/types/options.d.ts b/types/options.d.ts index 19c339f7450..0d1015d0081 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -174,7 +174,9 @@ export interface ComponentOptions< Props = DefaultProps, RawBindings = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, - Extends extends ComponentOptionsMixin = ComponentOptionsMixin + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Inject extends InjectOptions = {}, + InjectNames extends string = string, > { data?: Data props?: PropsDef @@ -225,7 +227,7 @@ export interface ComponentOptions< filters?: { [key: string]: Function } provide?: object | (() => object) - inject?: InjectOptions + inject?: Inject | InjectNames[] model?: { prop?: string @@ -342,8 +344,20 @@ export interface DirectiveOptions { export type InjectKey = string | symbol -export type InjectOptions = - | { - [key: string]: InjectKey | { from?: InjectKey; default?: any } - } - | string[] +export type InjectOptions = string[] | ObjectInjectOptions + +type ObjectInjectOptions = Record< + InjectKey, + string | symbol | { from?: string | symbol; default?: unknown } +> + +export type InjectToObject = + T extends string[] + ? { + [K in T[number]]?: unknown + } + : T extends ObjectInjectOptions + ? { + [K in keyof T]?: unknown + } + : never diff --git a/types/test/v3/define-component-test.tsx b/types/test/v3/define-component-test.tsx index 7e6d1968ca3..d2d22948d40 100644 --- a/types/test/v3/define-component-test.tsx +++ b/types/test/v3/define-component-test.tsx @@ -1225,3 +1225,210 @@ describe('should report non-existent properties in instance', () => { // @ts-expect-error instance2.foo }) + + +describe('inject', () => { + test('with object inject', () => { + defineComponent({ + props: { + a: String + }, + inject: { + foo: 'foo', + bar: 'bar', + }, + created() { + expectType(this.foo) + expectType(this.bar) + // @ts-expect-error + expectError(this.foobar = 1) + } + }) + }) + + test('with array inject', () => { + defineComponent({ + props: ['a', 'b'], + inject: ['foo', 'bar'], + created() { + expectType(this.foo) + expectType(this.bar) + // @ts-expect-error + expectError(this.foobar = 1) + } + }) + }) + + test('with no props', () => { + defineComponent({ + inject: { + foo: { + from: 'pfoo', + default: 'foo' + }, + bar: { + from: 'pbar', + default: 'bar' + }, + }, + created() { + expectType(this.foo) + expectType(this.bar) + // @ts-expect-error + expectError(this.foobar = 1) + } + }) + }) + + test('without inject', () => { + defineComponent({ + props: ['a', 'b'], + created() { + // @ts-expect-error + expectError(this.foo = 1) + // @ts-expect-error + expectError(this.bar = 1) + } + }) + }) + + test('define mixins w/ no props', () => { + const MixinA = defineComponent({ + inject: { + foo: 'foo' + }, + }) + const MixinB = defineComponent({ + inject: ['bar'], + }) + // with no props + defineComponent({ + mixins: [MixinA, MixinB], + created() { + expectType(this.foo) + expectType(this.bar) + } + }) + // with object props + defineComponent({ + mixins: [MixinA, MixinB], + props: { + baz: { + type: Number, + required: true + } + }, + created() { + expectType(this.foo) + expectType(this.bar) + expectType(this.baz) + } + }) + // with array props + defineComponent({ + mixins: [MixinA, MixinB], + props: ['baz'], + created() { + expectType(this.foo) + expectType(this.bar) + expectType(this.baz) + } + }) + }) + + test('define mixins w/ object props', () => { + const MixinA = defineComponent({ + props: { + a: String + }, + inject: { + foo: 'foo' + }, + }) + const MixinB = defineComponent({ + props: { + b: String + }, + inject: ['bar'], + }) + // with no props + defineComponent({ + mixins: [MixinA, MixinB], + created() { + expectType(this.foo) + expectType(this.bar) + } + }) + // with object props + defineComponent({ + mixins: [MixinA, MixinB], + props: { + baz: { + type: Number, + required: true + } + }, + created() { + expectType(this.foo) + expectType(this.bar) + expectType(this.baz) + } + }) + // with array props + defineComponent({ + mixins: [MixinA, MixinB], + props: ['baz'], + created() { + expectType(this.foo) + expectType(this.bar) + expectType(this.baz) + } + }) + }) + + test('define mixins w/ array props', () => { + const MixinA = defineComponent({ + props: ['a'], + inject: { + foo: 'foo' + }, + }) + const MixinB = defineComponent({ + props: ['b'], + inject: ['bar'], + }) + // with no props + defineComponent({ + mixins: [MixinA, MixinB], + created() { + expectType(this.foo) + expectType(this.bar) + } + }) + // with object props + defineComponent({ + mixins: [MixinA, MixinB], + props: { + baz: { + type: Number, + required: true + } + }, + created() { + expectType(this.foo) + expectType(this.bar) + expectType(this.baz) + } + }) + // with array props + defineComponent({ + mixins: [MixinA, MixinB], + props: ['baz'], + created() { + expectType(this.foo) + expectType(this.bar) + expectType(this.baz) + } + }) + }) +}) \ No newline at end of file diff --git a/types/v3-component-options.d.ts b/types/v3-component-options.d.ts index e2da34e753f..c9f4ce594aa 100644 --- a/types/v3-component-options.d.ts +++ b/types/v3-component-options.d.ts @@ -1,6 +1,6 @@ import { Vue } from './vue' import { VNode } from './vnode' -import { ComponentOptions as Vue2ComponentOptions } from './options' +import { ComponentOptions as Vue2ComponentOptions, InjectOptions } from './options' import { EmitsOptions, SetupContext } from './v3-setup-context' import { Data, LooseRequired, UnionToIntersection } from './common' import { @@ -82,9 +82,11 @@ export interface ComponentOptionsBase< Extends extends ComponentOptionsMixin, Emits extends EmitsOptions, EmitNames extends string = string, - Defaults = {} + Defaults = {}, + Inject extends InjectOptions = {}, + InjectNames extends string = string > extends Omit< - Vue2ComponentOptions, + Vue2ComponentOptions, 'data' | 'computed' | 'methods' | 'setup' | 'props' | 'mixins' | 'extends' >, ComponentCustomOptions { @@ -126,6 +128,7 @@ export type ComponentOptionsMixin = ComponentOptionsBase< any, any, any, + any, any > @@ -147,6 +150,8 @@ export type ComponentOptionsWithProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Emits extends EmitsOptions = {}, EmitsNames extends string = string, + Inject extends InjectOptions = {}, + InjectNames extends string = string, Props = ExtractPropTypes, Defaults = ExtractDefaultPropTypes > = ComponentOptionsBase< @@ -159,9 +164,11 @@ export type ComponentOptionsWithProps< Extends, Emits, EmitsNames, - Defaults + Defaults, + Inject, + InjectNames > & { - props?: PropsOptions + props: PropsOptions } & ThisType< CreateComponentPublicInstance< Props, @@ -171,7 +178,11 @@ export type ComponentOptionsWithProps< M, Mixin, Extends, - Emits + Emits, + Props, + Defaults, + false, + Inject > > @@ -185,6 +196,8 @@ export type ComponentOptionsWithArrayProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Emits extends EmitsOptions = {}, EmitsNames extends string = string, + Inject extends InjectOptions = {}, + InjectNames extends string = string, Props = Readonly<{ [key in PropNames]?: any }> > = ComponentOptionsBase< Props, @@ -196,9 +209,11 @@ export type ComponentOptionsWithArrayProps< Extends, Emits, EmitsNames, - {} + {}, + Inject, + InjectNames > & { - props?: PropNames[] + props: PropNames[] } & ThisType< CreateComponentPublicInstance< Props, @@ -208,7 +223,11 @@ export type ComponentOptionsWithArrayProps< M, Mixin, Extends, - Emits + Emits, + Props, + {}, + false, + Inject > > @@ -221,7 +240,9 @@ export type ComponentOptionsWithoutProps< Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Emits extends EmitsOptions = {}, - EmitsNames extends string = string + EmitsNames extends string = string, + Inject extends InjectOptions = {}, + InjectNames extends string = string > = ComponentOptionsBase< Props, RawBindings, @@ -232,7 +253,9 @@ export type ComponentOptionsWithoutProps< Extends, Emits, EmitsNames, - {} + {}, + Inject, + InjectNames > & { props?: undefined } & ThisType< @@ -244,7 +267,11 @@ export type ComponentOptionsWithoutProps< M, Mixin, Extends, - Emits + Emits, + Props, + {}, + false, + Inject > > diff --git a/types/v3-component-public-instance.d.ts b/types/v3-component-public-instance.d.ts index 1c55908ac73..9b39955a9e9 100644 --- a/types/v3-component-public-instance.d.ts +++ b/types/v3-component-public-instance.d.ts @@ -14,6 +14,7 @@ import { ComponentOptionsBase } from './v3-component-options' import { EmitFn, EmitsOptions } from './v3-setup-context' +import { InjectOptions, InjectToObject } from './options' /** * Custom properties added to component instances in any way and can be accessed through `this` @@ -33,7 +34,7 @@ export interface ComponentCustomProperties {} export type ComponentInstance = InstanceType -export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults' +export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults' | 'Inject' export type OptionTypesType< P = {}, @@ -41,14 +42,16 @@ export type OptionTypesType< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, - Defaults = {} + Defaults = {}, + Inject extends InjectOptions = {}, > = { P: P B: B D: D C: C M: M - Defaults: Defaults + Defaults: Defaults, + Inject: Inject } type IsDefaultMixinComponent = T extends ComponentOptionsMixin @@ -67,9 +70,10 @@ type MixinToOptionTypes = T extends ComponentOptionsBase< infer Extends, any, any, - infer Defaults + infer Defaults, + infer Inject > - ? OptionTypesType

& + ? OptionTypesType

& IntersectionMixin & IntersectionMixin : never @@ -102,6 +106,7 @@ export type CreateComponentPublicInstance< PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false, + Inject extends InjectOptions = {}, PublicMixin = IntersectionMixin & IntersectionMixin, PublicP = UnwrapMixinsType & EnsureNonVoid

, PublicB = UnwrapMixinsType & EnsureNonVoid, @@ -111,7 +116,9 @@ export type CreateComponentPublicInstance< PublicM extends MethodOptions = UnwrapMixinsType & EnsureNonVoid, PublicDefaults = UnwrapMixinsType & - EnsureNonVoid + EnsureNonVoid, + PublicInject extends InjectOptions = UnwrapMixinsType & + EnsureNonVoid, > = ComponentPublicInstance< PublicP, PublicB, @@ -121,7 +128,8 @@ export type CreateComponentPublicInstance< E, PublicProps, PublicDefaults, - MakeDefaultsOptional + MakeDefaultsOptional, + PublicInject > // public properties exposed on the proxy, which is used as the render context @@ -136,6 +144,7 @@ export type ComponentPublicInstance< PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false, + Inject extends InjectOptions = {}, Options = ComponentOptionsBase< any, any, @@ -162,7 +171,8 @@ export type ComponentPublicInstance< UnwrapNestedRefs & ExtractComputedReturns & M & - ComponentCustomProperties + ComponentCustomProperties & + InjectToObject interface Vue3Instance< D, diff --git a/types/v3-define-component.d.ts b/types/v3-define-component.d.ts index 03ef52d1856..4626953ec83 100644 --- a/types/v3-define-component.d.ts +++ b/types/v3-define-component.d.ts @@ -10,8 +10,9 @@ import { ComponentOptionsWithArrayProps, ComponentOptionsWithProps, ComponentOptionsMixin, - ComponentOptionsBase + ComponentOptionsBase, } from './v3-component-options' +import { InjectOptions } from './options'; import { ComponentPublicInstanceConstructor, CreateComponentPublicInstance @@ -30,12 +31,14 @@ export type DefineComponent< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, EE extends string = string, + inject extends InjectOptions = {}, + InjectNames extends string = string, Props = Readonly< PropsOrPropOptions extends ComponentPropsOptions ? ExtractPropTypes : PropsOrPropOptions >, - Defaults = ExtractDefaultPropTypes + Defaults = ExtractDefaultPropTypes, > = ComponentPublicInstanceConstructor< CreateComponentPublicInstance< Props, @@ -62,7 +65,9 @@ export type DefineComponent< Extends, E, EE, - Defaults + Defaults, + inject, + InjectNames > & { props: PropsOrPropOptions } @@ -78,7 +83,9 @@ export function defineComponent< Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Emits extends EmitsOptions = {}, - EmitsNames extends string = string + EmitsNames extends string = string, + Inject extends InjectOptions = {}, + InjectNames extends string = string >( options: { functional?: never } & ComponentOptionsWithoutProps< {}, @@ -89,9 +96,11 @@ export function defineComponent< Mixin, Extends, Emits, - EmitsNames + EmitsNames, + Inject, + InjectNames > -): DefineComponent<{}, RawBindings, D, C, M, Mixin, Extends, Emits> +): DefineComponent<{}, RawBindings, D, C, M, Mixin, Extends, Emits, string, Inject, InjectNames> /** * overload 2: object format with array props declaration @@ -109,7 +118,9 @@ export function defineComponent< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Emits extends EmitsOptions = {}, EmitsNames extends string = string, - PropsOptions extends ComponentPropsOptions = ComponentPropsOptions + PropsOptions extends ComponentPropsOptions = ComponentPropsOptions, + Inject extends InjectOptions = {}, + InjectNames extends string = string >( options: { functional?: never } & ComponentOptionsWithArrayProps< PropNames, @@ -120,7 +131,9 @@ export function defineComponent< Mixin, Extends, Emits, - EmitsNames + EmitsNames, + Inject, + InjectNames > ): DefineComponent< Readonly<{ [key in PropNames]?: any }>, @@ -130,7 +143,10 @@ export function defineComponent< M, Mixin, Extends, - Emits + Emits, + string, + Inject, + InjectNames > /** @@ -148,7 +164,9 @@ export function defineComponent< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Emits extends EmitsOptions = {}, EmitsNames extends string = string, - PropsOptions extends ComponentPropsOptions = ComponentPropsOptions + PropsOptions extends ComponentPropsOptions = ComponentPropsOptions, + Inject extends InjectOptions = {}, + InjectNames extends string = string >( options: HasDefined extends true ? { functional?: never } & ComponentOptionsWithProps< @@ -161,6 +179,8 @@ export function defineComponent< Extends, Emits, EmitsNames, + Inject, + InjectNames, Props > : { functional?: never } & ComponentOptionsWithProps< @@ -172,9 +192,11 @@ export function defineComponent< Mixin, Extends, Emits, - EmitsNames + EmitsNames, + Inject, + InjectNames > -): DefineComponent +): DefineComponent /** * overload 4.1: functional component with array props