]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
types(runtime-core): refactor defineComponent (#1883)
authorCarlos Rodrigues <david-181@hotmail.com>
Tue, 15 Sep 2020 15:46:11 +0000 (16:46 +0100)
committerGitHub <noreply@github.com>
Tue, 15 Sep 2020 15:46:11 +0000 (11:46 -0400)
packages/reactivity/src/baseHandlers.ts
packages/runtime-core/src/apiDefineComponent.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/componentPublicInstance.ts
packages/runtime-core/src/h.ts
packages/runtime-core/src/index.ts
test-dts/component.test-d.ts [new file with mode: 0644]

index b6f56f162cb2945fe56ea8f1ec3a2e94ceed80cb..2b7b29b03a8233115325ecd0413018a31026893b 100644 (file)
@@ -32,17 +32,17 @@ const readonlyGet = /*#__PURE__*/ createGetter(true)
 const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
 
 const arrayInstrumentations: Record<string, Function> = {}
-;['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
-  arrayInstrumentations[key] = function(...args: any[]): any {
-    const arr = toRaw(this) as any
-    for (let i = 0, l = (this as any).length; i < l; i++) {
+;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
+  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
+    const arr = toRaw(this)
+    for (let i = 0, l = this.length; i < l; i++) {
       track(arr, TrackOpTypes.GET, i + '')
     }
     // we run the method using the original args first (which may be reactive)
-    const res = arr[key](...args)
+    const res = (arr[key] as any)(...args)
     if (res === -1 || res === false) {
       // if that didn't work, run it again using raw values.
-      return arr[key](...args.map(toRaw))
+      return (arr[key] as any)(...args.map(toRaw))
     } else {
       return res
     }
index c054e65a8eb380bf615304af5790c29243706557..6fd77bc6e445a59d5503aabb50d698fdc42f5dfc 100644 (file)
@@ -6,22 +6,65 @@ import {
   ComponentOptionsWithObjectProps,
   ComponentOptionsMixin,
   RenderFunction,
-  UnwrapAsyncBindings
+  ComponentOptionsBase
 } from './componentOptions'
 import {
   SetupContext,
-  FunctionalComponent,
   AllowedComponentProps,
   ComponentCustomProps
 } from './component'
-import {
-  CreateComponentPublicInstance,
-  ComponentPublicInstanceConstructor
-} from './componentPublicInstance'
 import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
 import { EmitsOptions } from './componentEmits'
 import { isFunction } from '@vue/shared'
 import { VNodeProps } from './vnode'
+import {
+  CreateComponentPublicInstance,
+  ComponentPublicInstanceConstructor
+} from './componentPublicInstance'
+
+export type PublicProps = VNodeProps &
+  AllowedComponentProps &
+  ComponentCustomProps
+
+export type DefineComponent<
+  PropsOrPropOptions = any,
+  RawBindings = any,
+  D = any,
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions,
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+  E extends EmitsOptions = Record<string, any>,
+  EE extends string = string,
+  PP = PublicProps,
+  RequiredProps = Readonly<ExtractPropTypes<PropsOrPropOptions>>,
+  OptionalProps = Readonly<ExtractPropTypes<PropsOrPropOptions, false>>
+> = ComponentPublicInstanceConstructor<
+  CreateComponentPublicInstance<
+    OptionalProps,
+    RawBindings,
+    D,
+    C,
+    M,
+    Mixin,
+    Extends,
+    E,
+    PP & OptionalProps
+  > &
+    RequiredProps
+> &
+  ComponentOptionsBase<
+    RequiredProps,
+    RawBindings,
+    D,
+    C,
+    M,
+    Mixin,
+    Extends,
+    E,
+    EE
+  > &
+  PP
 
 // defineComponent is a utility that is primarily used for type inference
 // when declaring components. Type inference is provided in the component
@@ -35,21 +78,7 @@ export function defineComponent<Props, RawBindings = object>(
     props: Readonly<Props>,
     ctx: SetupContext
   ) => RawBindings | RenderFunction
-): ComponentPublicInstanceConstructor<
-  CreateComponentPublicInstance<
-    Props,
-    UnwrapAsyncBindings<RawBindings>,
-    {},
-    {},
-    {},
-    {},
-    {},
-    {},
-    // public props
-    VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
-  >
-> &
-  FunctionalComponent<Props>
+): DefineComponent<Props, RawBindings>
 
 // overload 2: object format with no props
 // (uses user defined props interface)
@@ -58,11 +87,11 @@ export function defineComponent<
   Props = {},
   RawBindings = {},
   D = {},
-  C extends ComputedOptions = {},
-  M extends MethodOptions = {},
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions,
   Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
-  E extends EmitsOptions = Record<string, any>,
+  E extends EmitsOptions = EmitsOptions,
   EE extends string = string
 >(
   options: ComponentOptionsWithoutProps<
@@ -76,30 +105,7 @@ export function defineComponent<
     E,
     EE
   >
-): ComponentPublicInstanceConstructor<
-  CreateComponentPublicInstance<
-    Props,
-    UnwrapAsyncBindings<RawBindings>,
-    D,
-    C,
-    M,
-    Mixin,
-    Extends,
-    E,
-    VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
-  >
-> &
-  ComponentOptionsWithoutProps<
-    Props,
-    RawBindings,
-    D,
-    C,
-    M,
-    Mixin,
-    Extends,
-    E,
-    EE
-  >
+): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>
 
 // overload 3: object format with array props declaration
 // props inferred as { [key in PropNames]?: any }
@@ -126,32 +132,17 @@ export function defineComponent<
     E,
     EE
   >
-): ComponentPublicInstanceConstructor<
-  // array props technically doesn't place any constraints on props in TSX before,
-  // but now we can export array props in TSX
-  CreateComponentPublicInstance<
-    Readonly<{ [key in PropNames]?: any }>,
-    UnwrapAsyncBindings<RawBindings>,
-    D,
-    C,
-    M,
-    Mixin,
-    Extends,
-    E,
-    AllowedComponentProps & ComponentCustomProps
-  >
-> &
-  ComponentOptionsWithArrayProps<
-    PropNames,
-    RawBindings,
-    D,
-    C,
-    M,
-    Mixin,
-    Extends,
-    E,
-    EE
-  >
+): DefineComponent<
+  Readonly<{ [key in PropNames]?: any }>,
+  RawBindings,
+  D,
+  C,
+  M,
+  Mixin,
+  Extends,
+  E,
+  EE
+>
 
 // overload 4: object format with object props declaration
 // see `ExtractPropTypes` in ./componentProps.ts
@@ -179,33 +170,20 @@ export function defineComponent<
     E,
     EE
   >
-): ComponentPublicInstanceConstructor<
-  CreateComponentPublicInstance<
-    ExtractPropTypes<PropsOptions, false>,
-    UnwrapAsyncBindings<RawBindings>,
-    D,
-    C,
-    M,
-    Mixin,
-    Extends,
-    E,
-    VNodeProps & AllowedComponentProps & ComponentCustomProps
-  > &
-    Readonly<ExtractPropTypes<PropsOptions>>
-> &
-  ComponentOptionsWithObjectProps<
-    PropsOptions,
-    RawBindings,
-    D,
-    C,
-    M,
-    Mixin,
-    Extends,
-    E,
-    EE
-  >
+): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
 
 // implementation, close to no-op
 export function defineComponent(options: unknown) {
   return isFunction(options) ? { setup: options, name: options.name } : options
 }
+
+defineComponent({
+  async setup() {
+    return {
+      a: 123
+    }
+  },
+  render() {
+    this.a
+  }
+})
index 6a768e26a098c376ec52e23029fc0e37e91cab5c..5a74541eb74e90725e07a9ed4a87ee4fe8617eb6 100644 (file)
@@ -26,7 +26,12 @@ import { warn } from './warning'
 import { ErrorCodes, callWithErrorHandling } from './errorHandling'
 import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
 import { Directive, validateDirectiveName } from './directives'
-import { applyOptions, ComponentOptions } from './componentOptions'
+import {
+  applyOptions,
+  ComponentOptions,
+  ComputedOptions,
+  MethodOptions
+} from './componentOptions'
 import {
   EmitsOptions,
   ObjectEmitsOptions,
@@ -118,13 +123,29 @@ export interface ClassComponent {
  * values, e.g. checking if its a function or not. This is mostly for internal
  * implementation code.
  */
-export type ConcreteComponent = ComponentOptions | FunctionalComponent<any, any>
+export type ConcreteComponent<
+  Props = {},
+  RawBindings = any,
+  D = any,
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions
+> =
+  | ComponentOptions<Props, RawBindings, D, C, M>
+  | FunctionalComponent<Props, any>
 
 /**
  * A type used in public APIs where a component type is expected.
  * The constructor type is an artificial type returned by defineComponent().
  */
-export type Component = ConcreteComponent | ComponentPublicInstanceConstructor
+export type Component<
+  Props = any,
+  RawBindings = any,
+  D = any,
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions
+> =
+  | ConcreteComponent<Props, RawBindings, D, C, M>
+  | ComponentPublicInstanceConstructor<Props>
 
 export { ComponentOptions }
 
index dec584ae8b49d96ddd4d95f2d7af5db5ab9ab29f..4d6291c95276f4a35aff16822e637a142df76d39 100644 (file)
@@ -72,8 +72,6 @@ export interface ComponentCustomOptions {}
 
 export type RenderFunction = () => VNodeChild
 
-export type UnwrapAsyncBindings<T> = T extends Promise<infer S> ? S : T
-
 export interface ComponentOptionsBase<
   Props,
   RawBindings,
@@ -92,7 +90,7 @@ export interface ComponentOptionsBase<
     this: void,
     props: Props,
     ctx: SetupContext<E>
-  ) => RawBindings | RenderFunction | void
+  ) => Promise<RawBindings> | RawBindings | RenderFunction | void
   name?: string
   template?: string | object // can be a direct DOM node
   // Note: we are intentionally using the signature-less `Function` type here
@@ -230,10 +228,29 @@ export type ComponentOptionsWithObjectProps<
     >
   >
 
-export type ComponentOptions =
-  | ComponentOptionsWithoutProps<any, any, any, any, any>
-  | ComponentOptionsWithObjectProps<any, any, any, any, any>
-  | ComponentOptionsWithArrayProps<any, any, any, any, any>
+export type ComponentOptions<
+  Props = {},
+  RawBindings = any,
+  D = any,
+  C extends ComputedOptions = any,
+  M extends MethodOptions = any,
+  Mixin extends ComponentOptionsMixin = any,
+  Extends extends ComponentOptionsMixin = any,
+  E extends EmitsOptions = any
+> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E> &
+  ThisType<
+    CreateComponentPublicInstance<
+      {},
+      RawBindings,
+      D,
+      C,
+      M,
+      Mixin,
+      Extends,
+      E,
+      Readonly<Props>
+    >
+  >
 
 export type ComponentOptionsMixin = ComponentOptionsBase<
   any,
@@ -638,17 +655,13 @@ export function applyOptions(
     onRenderTriggered(renderTriggered.bind(publicThis))
   }
   if (__DEV__ && beforeDestroy) {
-    warn(
-      `\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`
-    )
+    warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`)
   }
   if (beforeUnmount) {
     onBeforeUnmount(beforeUnmount.bind(publicThis))
   }
   if (__DEV__ && destroyed) {
-    warn(
-      `\`destroyed\` has been renamed to \`unmounted\`.`
-    )
+    warn(`\`destroyed\` has been renamed to \`unmounted\`.`)
   }
   if (unmounted) {
     onUnmounted(unmounted.bind(publicThis))
index 77fcbeeae26d39c0271e7daf31c4213794ba6b2c..2a9fa747339a5c2b929d8a992116230d2cafb4b7 100644 (file)
@@ -27,8 +27,7 @@ import {
   OptionTypesType,
   OptionTypesKeys,
   resolveMergedOptions,
-  isInBeforeCreate,
-  UnwrapAsyncBindings
+  isInBeforeCreate
 } from './componentOptions'
 import { EmitsOptions, EmitFn } from './componentEmits'
 import { Slots } from './componentSlots'
@@ -102,7 +101,18 @@ type UnwrapMixinsType<
 type EnsureNonVoid<T> = T extends void ? {} : T
 
 export type ComponentPublicInstanceConstructor<
-  T extends ComponentPublicInstance = ComponentPublicInstance<any>
+  T extends ComponentPublicInstance<
+    Props,
+    RawBindings,
+    D,
+    C,
+    M
+  > = ComponentPublicInstance<any>,
+  Props = any,
+  RawBindings = any,
+  D = any,
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions
 > = {
   __isFragment?: never
   __isTeleport?: never
@@ -138,6 +148,7 @@ export type CreateComponentPublicInstance<
   PublicProps,
   ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E>
 >
+
 // public properties exposed on the proxy, which is used as the render context
 // in templates (as `this` in the render option)
 export type ComponentPublicInstance<
@@ -169,7 +180,7 @@ export type ComponentPublicInstance<
     options?: WatchOptions
   ): WatchStopHandle
 } & P &
-  ShallowUnwrapRef<UnwrapAsyncBindings<B>> &
+  ShallowUnwrapRef<B> &
   D &
   ExtractComputedReturns<C> &
   M &
index 1939475b4ef58b637d02015ffb0b9c42749f1256..bf03247f2cb45bb66789de4aa31ef213759c000b 100644 (file)
@@ -10,9 +10,9 @@ import { Teleport, TeleportProps } from './components/Teleport'
 import { Suspense, SuspenseProps } from './components/Suspense'
 import { isObject, isArray } from '@vue/shared'
 import { RawSlots } from './componentSlots'
-import { FunctionalComponent, Component } from './component'
-import { ComponentOptions } from './componentOptions'
+import { FunctionalComponent, Component, ComponentOptions } from './component'
 import { EmitsOptions } from './componentEmits'
+import { DefineComponent } from './apiDefineComponent'
 
 // `h` is a more user-friendly version of `createVNode` that allows omitting the
 // props when possible. It is intended for manually written render functions.
@@ -50,7 +50,7 @@ type RawProps = VNodeProps & {
   __v_isVNode?: never
   // used to differ from Array children
   [Symbol.iterator]?: never
-} & { [key: string]: any }
+} & Record<string, any>
 
 type RawChildren =
   | string
@@ -112,10 +112,17 @@ export function h<P, E extends EmitsOptions = {}>(
 // catch-all for generic component types
 export function h(type: Component, children?: RawChildren): VNode
 
+// component without props
+export function h(
+  type: Component,
+  props: null,
+  children?: RawChildren | RawSlots
+): VNode
+
 // exclude `defineComponent` constructors
-export function h<T extends ComponentOptions | FunctionalComponent<{}>>(
-  type: T,
-  props?: RawProps | null,
+export function h<P>(
+  type: ComponentOptions<P>,
+  props?: (RawProps & P) | ({} extends P ? null : never),
   children?: RawChildren | RawSlots
 ): VNode
 
@@ -127,6 +134,14 @@ export function h<P>(
   children?: RawChildren | RawSlots
 ): VNode
 
+// fake constructor type returned by `defineComponent`
+export function h(type: DefineComponent, children?: RawChildren): VNode
+export function h<P>(
+  type: DefineComponent<P>,
+  props?: (RawProps & P) | ({} extends P ? null : never),
+  children?: RawChildren | RawSlots
+): VNode
+
 // Actual implementation
 export function h(type: any, propsOrChildren?: any, children?: any): VNode {
   const l = arguments.length
index e60c0a993977331cb60bcef97785d132afa81832..9449266111373409c5c277d3646b4cb506acb1d6 100644 (file)
@@ -41,7 +41,7 @@ export {
 } from './apiLifecycle'
 export { provide, inject } from './apiInject'
 export { nextTick } from './scheduler'
-export { defineComponent } from './apiDefineComponent'
+export { defineComponent, DefineComponent } from './apiDefineComponent'
 export { defineAsyncComponent } from './apiAsyncComponent'
 
 // Advanced API ----------------------------------------------------------------
diff --git a/test-dts/component.test-d.ts b/test-dts/component.test-d.ts
new file mode 100644 (file)
index 0000000..990e370
--- /dev/null
@@ -0,0 +1,413 @@
+import {
+  describe,
+  Component,
+  defineComponent,
+  PropType,
+  ref,
+  Ref,
+  expectError,
+  expectType,
+  ShallowUnwrapRef,
+  FunctionalComponent,
+  ComponentPublicInstance
+} from './index'
+
+declare function extractComponentOptions<Props, RawBindings>(
+  obj: Component<Props, RawBindings>
+): {
+  props: Props
+  rawBindings: RawBindings
+  setup: ShallowUnwrapRef<RawBindings>
+}
+
+describe('object props', () => {
+  interface ExpectedProps {
+    a?: number | undefined
+    b: string
+    e?: Function
+    bb: string
+    bbb: string
+    cc?: string[] | undefined
+    dd: { n: 1 }
+    ee?: () => string
+    ff?: (a: number, b: string) => { a: boolean }
+    ccc?: string[] | undefined
+    ddd: string[]
+    eee: () => { a: string }
+    fff: (a: number, b: string) => { a: boolean }
+    hhh: boolean
+    ggg: 'foo' | 'bar'
+    ffff: (a: number, b: string) => { a: boolean }
+    validated?: string
+  }
+
+  describe('defineComponent', () => {
+    const MyComponent = defineComponent({
+      props: {
+        a: Number,
+        // required should make property non-void
+        b: {
+          type: String,
+          required: true
+        },
+        e: Function,
+        // default value should infer type and make it non-void
+        bb: {
+          default: 'hello'
+        },
+        bbb: {
+          // Note: default function value requires arrow syntax + explicit
+          // annotation
+          default: (props: any) => (props.bb as string) || 'foo'
+        },
+        // explicit type casting
+        cc: Array as PropType<string[]>,
+        // required + type casting
+        dd: {
+          type: Object as PropType<{ n: 1 }>,
+          required: true
+        },
+        // return type
+        ee: Function as PropType<() => string>,
+        // arguments + object return
+        ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
+        // explicit type casting with constructor
+        ccc: Array as () => string[],
+        // required + contructor type casting
+        ddd: {
+          type: Array as () => string[],
+          required: true
+        },
+        // required + object return
+        eee: {
+          type: Function as PropType<() => { a: string }>,
+          required: true
+        },
+        // required + arguments + object return
+        fff: {
+          type: Function as PropType<(a: number, b: string) => { a: boolean }>,
+          required: true
+        },
+        hhh: {
+          type: Boolean,
+          required: true
+        },
+        // default + type casting
+        ggg: {
+          type: String as PropType<'foo' | 'bar'>,
+          default: 'foo'
+        },
+        // default + function
+        ffff: {
+          type: Function as PropType<(a: number, b: string) => { a: boolean }>,
+          default: (_a: number, _b: string) => ({ a: true })
+        },
+        validated: {
+          type: String,
+          // validator requires explicit annotation
+          validator: (val: unknown) => val !== ''
+        }
+      },
+      setup(props) {
+        return {
+          setupA: 1,
+          setupB: ref(1),
+          setupC: {
+            a: ref(2)
+          },
+          setupProps: props
+        }
+      }
+    })
+
+    const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
+
+    // props
+    expectType<ExpectedProps['a']>(props.a)
+    expectType<ExpectedProps['b']>(props.b)
+    expectType<ExpectedProps['e']>(props.e)
+    expectType<ExpectedProps['bb']>(props.bb)
+    expectType<ExpectedProps['bbb']>(props.bbb)
+    expectType<ExpectedProps['cc']>(props.cc)
+    expectType<ExpectedProps['dd']>(props.dd)
+    expectType<ExpectedProps['ee']>(props.ee)
+    expectType<ExpectedProps['ff']>(props.ff)
+    expectType<ExpectedProps['ccc']>(props.ccc)
+    expectType<ExpectedProps['ddd']>(props.ddd)
+    expectType<ExpectedProps['eee']>(props.eee)
+    expectType<ExpectedProps['fff']>(props.fff)
+    expectType<ExpectedProps['hhh']>(props.hhh)
+    expectType<ExpectedProps['ggg']>(props.ggg)
+    expectType<ExpectedProps['ffff']>(props.ffff)
+    expectType<ExpectedProps['validated']>(props.validated)
+
+    // raw bindings
+    expectType<Number>(rawBindings.setupA)
+    expectType<Ref<Number>>(rawBindings.setupB)
+    expectType<Ref<Number>>(rawBindings.setupC.a)
+    expectType<Number>(rawBindings.setupA)
+
+    // raw bindings props
+    expectType<ExpectedProps['a']>(rawBindings.setupProps.a)
+    expectType<ExpectedProps['b']>(rawBindings.setupProps.b)
+    expectType<ExpectedProps['e']>(rawBindings.setupProps.e)
+    expectType<ExpectedProps['bb']>(rawBindings.setupProps.bb)
+    expectType<ExpectedProps['bbb']>(rawBindings.setupProps.bbb)
+    expectType<ExpectedProps['cc']>(rawBindings.setupProps.cc)
+    expectType<ExpectedProps['dd']>(rawBindings.setupProps.dd)
+    expectType<ExpectedProps['ee']>(rawBindings.setupProps.ee)
+    expectType<ExpectedProps['ff']>(rawBindings.setupProps.ff)
+    expectType<ExpectedProps['ccc']>(rawBindings.setupProps.ccc)
+    expectType<ExpectedProps['ddd']>(rawBindings.setupProps.ddd)
+    expectType<ExpectedProps['eee']>(rawBindings.setupProps.eee)
+    expectType<ExpectedProps['fff']>(rawBindings.setupProps.fff)
+    expectType<ExpectedProps['hhh']>(rawBindings.setupProps.hhh)
+    expectType<ExpectedProps['ggg']>(rawBindings.setupProps.ggg)
+    expectType<ExpectedProps['ffff']>(rawBindings.setupProps.ffff)
+    expectType<ExpectedProps['validated']>(rawBindings.setupProps.validated)
+
+    // setup
+    expectType<Number>(setup.setupA)
+    expectType<Number>(setup.setupB)
+    expectType<Ref<Number>>(setup.setupC.a)
+    expectType<Number>(setup.setupA)
+
+    // raw bindings props
+    expectType<ExpectedProps['a']>(setup.setupProps.a)
+    expectType<ExpectedProps['b']>(setup.setupProps.b)
+    expectType<ExpectedProps['e']>(setup.setupProps.e)
+    expectType<ExpectedProps['bb']>(setup.setupProps.bb)
+    expectType<ExpectedProps['bbb']>(setup.setupProps.bbb)
+    expectType<ExpectedProps['cc']>(setup.setupProps.cc)
+    expectType<ExpectedProps['dd']>(setup.setupProps.dd)
+    expectType<ExpectedProps['ee']>(setup.setupProps.ee)
+    expectType<ExpectedProps['ff']>(setup.setupProps.ff)
+    expectType<ExpectedProps['ccc']>(setup.setupProps.ccc)
+    expectType<ExpectedProps['ddd']>(setup.setupProps.ddd)
+    expectType<ExpectedProps['eee']>(setup.setupProps.eee)
+    expectType<ExpectedProps['fff']>(setup.setupProps.fff)
+    expectType<ExpectedProps['hhh']>(setup.setupProps.hhh)
+    expectType<ExpectedProps['ggg']>(setup.setupProps.ggg)
+    expectType<ExpectedProps['ffff']>(setup.setupProps.ffff)
+    expectType<ExpectedProps['validated']>(setup.setupProps.validated)
+  })
+
+  describe('options', () => {
+    const MyComponent = {
+      props: {
+        a: Number,
+        // required should make property non-void
+        b: {
+          type: String,
+          required: true
+        },
+        e: Function,
+        // default value should infer type and make it non-void
+        bb: {
+          default: 'hello'
+        },
+        bbb: {
+          // Note: default function value requires arrow syntax + explicit
+          // annotation
+          default: (props: any) => (props.bb as string) || 'foo'
+        },
+        // explicit type casting
+        cc: Array as PropType<string[]>,
+        // required + type casting
+        dd: {
+          type: Object as PropType<{ n: 1 }>,
+          required: true
+        },
+        // return type
+        ee: Function as PropType<() => string>,
+        // arguments + object return
+        ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
+        // explicit type casting with constructor
+        ccc: Array as () => string[],
+        // required + contructor type casting
+        ddd: {
+          type: Array as () => string[],
+          required: true
+        },
+        // required + object return
+        eee: {
+          type: Function as PropType<() => { a: string }>,
+          required: true
+        },
+        // required + arguments + object return
+        fff: {
+          type: Function as PropType<(a: number, b: string) => { a: boolean }>,
+          required: true
+        },
+        hhh: {
+          type: Boolean,
+          required: true
+        },
+        // default + type casting
+        ggg: {
+          type: String as PropType<'foo' | 'bar'>,
+          default: 'foo'
+        },
+        // default + function
+        ffff: {
+          type: Function as PropType<(a: number, b: string) => { a: boolean }>,
+          default: (_a: number, _b: string) => ({ a: true })
+        },
+        validated: {
+          type: String,
+          // validator requires explicit annotation
+          validator: (val: unknown) => val !== ''
+        }
+      },
+
+      setup() {
+        return {
+          setupA: 1
+        }
+      }
+    } as const
+
+    const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
+
+    // props
+    expectType<ExpectedProps['a']>(props.a)
+    expectType<ExpectedProps['b']>(props.b)
+    expectType<ExpectedProps['e']>(props.e)
+    expectType<ExpectedProps['bb']>(props.bb)
+    expectType<ExpectedProps['bbb']>(props.bbb)
+    expectType<ExpectedProps['cc']>(props.cc)
+    expectType<ExpectedProps['dd']>(props.dd)
+    expectType<ExpectedProps['ee']>(props.ee)
+    expectType<ExpectedProps['ff']>(props.ff)
+    expectType<ExpectedProps['ccc']>(props.ccc)
+    expectType<ExpectedProps['ddd']>(props.ddd)
+    expectType<ExpectedProps['eee']>(props.eee)
+    expectType<ExpectedProps['fff']>(props.fff)
+    expectType<ExpectedProps['hhh']>(props.hhh)
+    expectType<ExpectedProps['ggg']>(props.ggg)
+    // expectType<ExpectedProps['ffff']>(props.ffff) // todo fix
+    expectType<ExpectedProps['validated']>(props.validated)
+
+    // rawBindings
+    expectType<Number>(rawBindings.setupA)
+
+    //setup
+    expectType<Number>(setup.setupA)
+  })
+})
+
+describe('array props', () => {
+  describe('defineComponent', () => {
+    const MyComponent = defineComponent({
+      props: ['a', 'b'],
+      setup() {
+        return {
+          c: 1
+        }
+      }
+    })
+
+    const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
+
+    // @ts-expect-error props should be readonly
+    expectError((props.a = 1))
+    expectType<any>(props.a)
+    expectType<any>(props.b)
+
+    expectType<number>(rawBindings.c)
+    expectType<number>(setup.c)
+  })
+
+  describe('options', () => {
+    const MyComponent = {
+      props: ['a', 'b'] as const,
+      setup() {
+        return {
+          c: 1
+        }
+      }
+    }
+
+    const { props, rawBindings, setup } = extractComponentOptions(MyComponent)
+
+    // @ts-expect-error props should be readonly
+    expectError((props.a = 1))
+
+    // TODO infer the correct keys
+    // expectType<any>(props.a)
+    // expectType<any>(props.b)
+
+    expectType<number>(rawBindings.c)
+    expectType<number>(setup.c)
+  })
+})
+
+describe('no props', () => {
+  describe('defineComponent', () => {
+    const MyComponent = defineComponent({
+      setup() {
+        return {
+          setupA: 1
+        }
+      }
+    })
+
+    const { rawBindings, setup } = extractComponentOptions(MyComponent)
+
+    expectType<number>(rawBindings.setupA)
+    expectType<number>(setup.setupA)
+  })
+
+  describe('options', () => {
+    const MyComponent = {
+      setup() {
+        return {
+          setupA: 1
+        }
+      }
+    }
+
+    const { rawBindings, setup } = extractComponentOptions(MyComponent)
+
+    expectType<number>(rawBindings.setupA)
+    expectType<number>(setup.setupA)
+  })
+})
+
+describe('functional', () => {
+  // TODO `props.foo` is `number|undefined`
+  //   describe('defineComponent', () => {
+  //     const MyComponent = defineComponent((props: { foo: number }) => {})
+
+  //     const { props } = extractComponentOptions(MyComponent)
+
+  //     expectType<number>(props.foo)
+  //   })
+
+  describe('function', () => {
+    const MyComponent = (props: { foo: number }) => props.foo
+    const { props } = extractComponentOptions(MyComponent)
+
+    expectType<number>(props.foo)
+  })
+
+  describe('typed', () => {
+    const MyComponent: FunctionalComponent<{ foo: number }> = (_, _2) => {}
+
+    const { props } = extractComponentOptions(MyComponent)
+
+    expectType<number>(props.foo)
+  })
+})
+
+declare type VueClass<Props = {}> = {
+  new (): ComponentPublicInstance<Props>
+}
+
+describe('class', () => {
+  const MyComponent: VueClass<{ foo: number }> = {} as any
+
+  const { props } = extractComponentOptions(MyComponent)
+
+  expectType<number>(props.foo)
+})