]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(types): map declared emits to onXXX props in inferred prop types (#3926)
authorAmour1688 <lcz_1996@foxmail.com>
Mon, 19 Jul 2021 22:22:19 +0000 (06:22 +0800)
committerGitHub <noreply@github.com>
Mon, 19 Jul 2021 22:22:19 +0000 (18:22 -0400)
.prettierrc
package.json
packages/runtime-core/src/apiDefineComponent.ts
packages/runtime-core/src/componentEmits.ts
packages/runtime-core/src/componentOptions.ts
test-dts/defineComponent.test-d.tsx
yarn.lock

index f5a1bdcdd2daf271a87314a475c36ca723c0b499..ef93d94821a8486b23231d10bf3ce939ecaaa7b8 100644 (file)
@@ -1,3 +1,5 @@
 semi: false
 singleQuote: true
 printWidth: 80
+trailingComma: 'none'
+arrowParens: 'avoid'
index c8ca74c556ed13341cd81ea126945903bc2c64fb..2a220c57abfa8bc158336c6e92368786aaaa97dd 100644 (file)
@@ -68,7 +68,7 @@
     "lint-staged": "^10.2.10",
     "minimist": "^1.2.0",
     "npm-run-all": "^4.1.5",
-    "prettier": "~1.14.0",
+    "prettier": "^2.3.1",
     "puppeteer": "^10.0.0",
     "rollup": "~2.38.5",
     "rollup-plugin-node-builtins": "^2.1.2",
index 333d18a30f304f185df28798599d19f815dd5628..1560003bcd4b642f5e53b9284e119f4d2517ef63 100644 (file)
@@ -18,7 +18,7 @@ import {
   ComponentPropsOptions,
   ExtractDefaultPropTypes
 } from './componentProps'
-import { EmitsOptions } from './componentEmits'
+import { EmitsOptions, EmitsToProps } from './componentEmits'
 import { isFunction } from '@vue/shared'
 import { VNodeProps } from './vnode'
 import {
@@ -41,7 +41,7 @@ export type DefineComponent<
   E extends EmitsOptions = Record<string, any>,
   EE extends string = string,
   PP = PublicProps,
-  Props = Readonly<ExtractPropTypes<PropsOrPropOptions>>,
+  Props = Readonly<ExtractPropTypes<PropsOrPropOptions>> & EmitsToProps<E>,
   Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
 > = ComponentPublicInstanceConstructor<
   CreateComponentPublicInstance<
@@ -102,7 +102,7 @@ export function defineComponent<
   EE extends string = string
 >(
   options: ComponentOptionsWithoutProps<
-    Props,
+    Props & EmitsToProps<E>,
     RawBindings,
     D,
     C,
index 52900ea31761b6b25098cd2bf448ff7301e4949b..b98f8c34e847ab69572dee6773f7318f1c2005e0 100644 (file)
@@ -31,22 +31,38 @@ export type ObjectEmitsOptions = Record<
   string,
   ((...args: any[]) => any) | null
 >
+
 export type EmitsOptions = ObjectEmitsOptions | string[]
 
+export type EmitsToProps<T extends EmitsOptions> = T extends string[]
+  ? {
+      [K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
+    }
+  : T extends ObjectEmitsOptions
+  ? {
+      [K in string &
+        `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
+        ? T[Uncapitalize<C>] extends null
+          ? (...args: any[]) => any
+          : T[Uncapitalize<C>]
+        : never
+    }
+  : {}
+
 export type EmitFn<
   Options = ObjectEmitsOptions,
   Event extends keyof Options = keyof Options
 > = Options extends Array<infer V>
   ? (event: V, ...args: any[]) => void
   : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
-    ? (event: string, ...args: any[]) => void
-    : UnionToIntersection<
-        {
-          [key in Event]: Options[key] extends ((...args: infer Args) => any)
-            ? (event: key, ...args: Args) => void
-            : (event: key, ...args: any[]) => void
-        }[Event]
-      >
+  ? (event: string, ...args: any[]) => void
+  : UnionToIntersection<
+      {
+        [key in Event]: Options[key] extends (...args: infer Args) => any
+          ? (event: key, ...args: Args) => void
+          : (event: key, ...args: any[]) => void
+      }[Event]
+    >
 
 export function emit(
   instance: ComponentInternalInstance,
index e8cc5f75d67bb21718e77b160f921a696508cebe..bffb215cd5cb003e76bcaeb217146f650d8d9832 100644 (file)
@@ -51,7 +51,7 @@ import {
   ExtractPropTypes,
   ExtractDefaultPropTypes
 } from './componentProps'
-import { EmitsOptions } from './componentEmits'
+import { EmitsOptions, EmitsToProps } from './componentEmits'
 import { Directive } from './directives'
 import {
   CreateComponentPublicInstance,
@@ -91,16 +91,18 @@ export interface ComponentCustomOptions {}
 export type RenderFunction = () => VNodeChild
 
 type ExtractOptionProp<T> = T extends ComponentOptionsBase<
-  infer P,
-  any,
-  any,
-  any,
-  any,
-  any,
-  any,
-  any
+  infer P, // Props
+  any, // RawBindings
+  any, // D
+  any, // C
+  any, // M
+  any, // Mixin
+  any, // Extends
+  any // EmitsOptions
 >
-  ? unknown extends P ? {} : P
+  ? unknown extends P
+    ? {}
+    : P
   : {}
 
 export interface ComponentOptionsBase<
@@ -114,8 +116,7 @@ export interface ComponentOptionsBase<
   E extends EmitsOptions,
   EE extends string = string,
   Defaults = {}
->
-  extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
+> extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
     ComponentInternalOptions,
     ComponentCustomOptions {
   setup?: (
@@ -220,9 +221,10 @@ export type ComponentOptionsWithoutProps<
   Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
-  EE extends string = string
+  EE extends string = string,
+  PE = Props & EmitsToProps<E>
 > = ComponentOptionsBase<
-  Props,
+  PE,
   RawBindings,
   D,
   C,
@@ -235,7 +237,7 @@ export type ComponentOptionsWithoutProps<
 > & {
   props?: undefined
 } & ThisType<
-    CreateComponentPublicInstance<{}, RawBindings, D, C, M, Mixin, Extends, E>
+    CreateComponentPublicInstance<PE, RawBindings, D, C, M, Mixin, Extends, E>
   >
 
 export type ComponentOptionsWithArrayProps<
@@ -248,7 +250,7 @@ export type ComponentOptionsWithArrayProps<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
-  Props = Readonly<{ [key in PropNames]?: any }>
+  Props = Readonly<{ [key in PropNames]?: any }> & EmitsToProps<E>
 > = ComponentOptionsBase<
   Props,
   RawBindings,
@@ -285,7 +287,7 @@ export type ComponentOptionsWithObjectProps<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
-  Props = Readonly<ExtractPropTypes<PropsOptions>>,
+  Props = Readonly<ExtractPropTypes<PropsOptions>> & EmitsToProps<E>,
   Defaults = ExtractDefaultPropTypes<PropsOptions>
 > = ComponentOptionsBase<
   Props,
@@ -365,7 +367,9 @@ export interface MethodOptions {
 export type ExtractComputedReturns<T extends any> = {
   [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
     ? TReturn
-    : T[key] extends (...args: any[]) => infer TReturn ? TReturn : never
+    : T[key] extends (...args: any[]) => infer TReturn
+    ? TReturn
+    : never
 }
 
 export type ObjectWatchOptionItem = {
@@ -471,7 +475,7 @@ interface LegacyOptions<
   __differentiator?: keyof D | keyof C | keyof M
 }
 
-type MergedHook<T = (() => void)> = T | T[]
+type MergedHook<T = () => void> = T | T[]
 
 export type MergedComponentOptions = ComponentOptions &
   MergedComponentOptionsOverride
@@ -679,8 +683,8 @@ export function applyOptions(instance: ComponentInternalInstance) {
       const get = isFunction(opt)
         ? opt.bind(publicThis, publicThis)
         : isFunction(opt.get)
-          ? opt.get.bind(publicThis, publicThis)
-          : NOOP
+        ? opt.get.bind(publicThis, publicThis)
+        : NOOP
       if (__DEV__ && get === NOOP) {
         warn(`Computed property "${key}" has no getter.`)
       }
@@ -688,12 +692,12 @@ export function applyOptions(instance: ComponentInternalInstance) {
         !isFunction(opt) && isFunction(opt.set)
           ? opt.set.bind(publicThis)
           : __DEV__
-            ? () => {
-                warn(
-                  `Write operation failed: computed property "${key}" is readonly.`
-                )
-              }
-            : NOOP
+          ? () => {
+              warn(
+                `Write operation failed: computed property "${key}" is readonly.`
+              )
+            }
+          : NOOP
       const c = computed({
         get,
         set
@@ -1006,10 +1010,11 @@ function mergeDataFn(to: any, from: any) {
     return from
   }
   return function mergedDataFn(this: ComponentPublicInstance) {
-    return (__COMPAT__ &&
-    isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, null)
-      ? deepMergeData
-      : extend)(
+    return (
+      __COMPAT__ && isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, null)
+        ? deepMergeData
+        : extend
+    )(
       isFunction(to) ? to.call(this, this) : to,
       isFunction(from) ? from.call(this, this) : from
     )
index 3f59bd6ad397fed9fffeaac963bf9093948e5ffa..dba11f20b9b4718dce6712b091f8b533302f563d 100644 (file)
@@ -469,6 +469,7 @@ describe('type inference w/ options API', () => {
 
 describe('with mixins', () => {
   const MixinA = defineComponent({
+    emits: ['bar'],
     props: {
       aP1: {
         type: String,
@@ -523,6 +524,7 @@ describe('with mixins', () => {
   })
   const MyComponent = defineComponent({
     mixins: [MixinA, MixinB, MixinC, MixinD],
+    emits: ['click'],
     props: {
       // required should make property non-void
       z: {
@@ -552,6 +554,9 @@ describe('with mixins', () => {
     setup(props) {
       expectType<string>(props.z)
       // props
+      expectType<((...args: any[]) => any) | undefined>(props.onClick)
+      // from Base
+      expectType<((...args: any[]) => any) | undefined>(props.onBar)
       expectType<string>(props.aP1)
       expectType<boolean | undefined>(props.aP2)
       expectType<any>(props.bP1)
@@ -561,6 +566,9 @@ describe('with mixins', () => {
     render() {
       const props = this.$props
       // props
+      expectType<((...args: any[]) => any) | undefined>(props.onClick)
+      // from Base
+      expectType<((...args: any[]) => any) | undefined>(props.onBar)
       expectType<string>(props.aP1)
       expectType<boolean | undefined>(props.aP2)
       expectType<any>(props.bP1)
@@ -688,6 +696,7 @@ describe('with extends', () => {
 
 describe('extends with mixins', () => {
   const Mixin = defineComponent({
+    emits: ['bar'],
     props: {
       mP1: {
         type: String,
@@ -706,6 +715,7 @@ describe('extends with mixins', () => {
     }
   })
   const Base = defineComponent({
+    emits: ['foo'],
     props: {
       p1: Boolean,
       p2: {
@@ -731,6 +741,7 @@ describe('extends with mixins', () => {
   const MyComponent = defineComponent({
     extends: Base,
     mixins: [Mixin],
+    emits: ['click'],
     props: {
       // required should make property non-void
       z: {
@@ -741,6 +752,11 @@ describe('extends with mixins', () => {
     render() {
       const props = this.$props
       // props
+      expectType<((...args: any[]) => any) | undefined>(props.onClick)
+      // from Mixin
+      expectType<((...args: any[]) => any) | undefined>(props.onBar)
+      // from Base
+      expectType<((...args: any[]) => any) | undefined>(props.onFoo)
       expectType<boolean | undefined>(props.p1)
       expectType<number>(props.p2)
       expectType<string>(props.z)
@@ -879,6 +895,8 @@ describe('emits', () => {
       input: (b: string) => b.length > 1
     },
     setup(props, { emit }) {
+      expectType<((n: number) => boolean) | undefined>(props.onClick)
+      expectType<((b: string) => boolean) | undefined>(props.onInput)
       emit('click', 1)
       emit('input', 'foo')
       //  @ts-expect-error
@@ -931,6 +949,8 @@ describe('emits', () => {
   defineComponent({
     emits: ['foo', 'bar'],
     setup(props, { emit }) {
+      expectType<((...args: any[]) => any) | undefined>(props.onFoo)
+      expectType<((...args: any[]) => any) | undefined>(props.onBar)
       emit('foo')
       emit('foo', 123)
       emit('bar')
@@ -972,10 +992,9 @@ describe('emits', () => {
 })
 
 describe('componentOptions setup should be `SetupContext`', () => {
-  expect<ComponentOptions['setup']>({} as (
-    props: Record<string, any>,
-    ctx: SetupContext
-  ) => any)
+  expect<ComponentOptions['setup']>(
+    {} as (props: Record<string, any>, ctx: SetupContext) => any
+  )
 })
 
 describe('extract instance type', () => {
index f66481b73ee3e401e6137ac3734f944c296594e2..8f9415ed2304b6ad609cd62980ac9a9a61c29d25 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -5630,10 +5630,10 @@ prelude-ls@~1.1.2:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
-prettier@~1.14.0:
-  version "1.14.3"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895"
-  integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==
+prettier@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6"
+  integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==
 
 pretty-format@^26.0.0, pretty-format@^26.6.2:
   version "26.6.2"