import { Component, ComponentInstance, ComponentClass, APIMethods, LifecycleMethods } from './component' import { isArray, isObject, isFunction } from '@vue/shared' import { normalizePropsOptions } from './componentProps' export type Data = Record export interface ComponentClassOptions

{ props?: ComponentPropsOptions

computed?: ComponentComputedOptions watch?: ComponentWatchOptions displayName?: string fromOptions?: boolean } export interface ComponentOptions< P = {}, D = {}, This = ComponentInstance > extends ComponentClassOptions, Partial>, Partial { // TODO other options readonly [key: string]: any } export type ComponentPropsOptions

= { [K in keyof P]: PropValidator } export type Prop = { (): T } | { new (...args: any[]): T & object } export type PropType = Prop | Prop[] export type PropValidator = PropOptions | PropType export interface PropOptions { type?: PropType | true | null required?: boolean default?: T | null | undefined | (() => T | null | undefined) validator?(value: T): boolean } export interface ComponentComputedOptions { [key: string]: ((this: This, c: This) => any) | SingleComputedOptions } type SingleComputedOptions = { get: (this: This, c: This) => any set?: (value: any) => void cache?: boolean } export interface ComponentWatchOptions { [key: string]: ComponentWatchOption } export type ComponentWatchOption = | WatchHandler | WatchHandler[] | WatchOptionsWithHandler | string export type WatchHandler = ( this: This, val: any, oldVal: any ) => void export interface WatchOptionsWithHandler extends WatchOptions { handler: WatchHandler } export interface WatchOptions { sync?: boolean deep?: boolean immediate?: boolean } type ReservedKeys = { [K in keyof (APIMethods & LifecycleMethods)]: 1 } export const reservedMethods: ReservedKeys = { data: 1, render: 1, beforeCreate: 1, created: 1, beforeMount: 1, mounted: 1, beforeUpdate: 1, updated: 1, beforeUnmount: 1, unmounted: 1, errorCaptured: 1, activated: 1, deactivated: 1, renderTracked: 1, renderTriggered: 1 } // This is called in the base component constructor and the return value is // set on the instance as $options. export function resolveComponentOptionsFromClass( Class: ComponentClass ): ComponentOptions { if (Class.hasOwnProperty('options')) { return Class.options as ComponentOptions } let options = {} as any const staticDescriptors = Object.getOwnPropertyDescriptors(Class) for (const key in staticDescriptors) { const { enumerable, get, value } = staticDescriptors[key] if (enumerable || get) { options[key] = get ? get() : value } } const instanceDescriptors = Object.getOwnPropertyDescriptors(Class.prototype) for (const key in instanceDescriptors) { const { get, value } = instanceDescriptors[key] if (get) { // computed properties ;(options.computed || (options.computed = {}))[key] = get // there's no need to do anything for the setter // as it's already defined on the prototype } else if (isFunction(value) && key !== 'constructor') { if (key in reservedMethods) { options[key] = value } else { ;(options.methods || (options.methods = {}))[key] = value } } } if (options.props) { options.props = normalizePropsOptions(options.props) } const ParentClass = Object.getPrototypeOf(Class) if (ParentClass !== Component) { const parentOptions = resolveComponentOptionsFromClass(ParentClass) options = mergeComponentOptions(parentOptions, options) } Class.options = options return options } export function mergeComponentOptions(to: any, from: any): ComponentOptions { const res: any = Object.assign({}, to) if (from.mixins) { from.mixins.forEach((mixin: any) => { from = mergeComponentOptions(from, mixin) }) } for (const key in from) { const value = from[key] const existing = res[key] if (isFunction(value) && isFunction(existing)) { if (key === 'data') { // for data we need to merge the returned value res[key] = mergeDataFn(existing, value) } else if (/^render|^errorCaptured/.test(key)) { // render, renderTracked, renderTriggered & errorCaptured // are never merged res[key] = value } else { // merge lifecycle hooks res[key] = function(...args: any[]) { existing.call(this, ...args) value.call(this, ...args) } } } else if (isArray(value) && isArray(existing)) { res[key] = existing.concat(value) } else if (isObject(value) && isObject(existing)) { res[key] = Object.assign({}, existing, value) } else { res[key] = value } } return res } export function mergeDataFn(a: Function, b: Function): Function { // TODO: backwards compat requires recursive merge, // but maybe we should just warn if we detect clashing keys return function() { return Object.assign(a.call(this), b.call(this)) } }