types(runtime-core): support plugin options type inference (#3969)

This commit is contained in:
Tony Trinh 2022-11-13 19:13:32 -06:00 committed by GitHub
parent 584eae60d1
commit c513126c5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 5 deletions

View File

@ -31,7 +31,13 @@ import { ObjectEmitsOptions } from './componentEmits'
export interface App<HostElement = any> { export interface App<HostElement = any> {
version: string version: string
config: AppConfig config: AppConfig
use(plugin: Plugin, ...options: any[]): this
use<Options extends unknown[]>(
plugin: Plugin<Options>,
...options: Options
): this
use<Options>(plugin: Plugin<Options>, options: Options): this
mixin(mixin: ComponentOptions): this mixin(mixin: ComponentOptions): this
component(name: string): Component | undefined component(name: string): Component | undefined
component(name: string, component: Component): this component(name: string, component: Component): this
@ -140,12 +146,16 @@ export interface AppContext {
filters?: Record<string, Function> filters?: Record<string, Function>
} }
type PluginInstallFunction = (app: App, ...options: any[]) => any type PluginInstallFunction<Options> = Options extends unknown[]
? (app: App, ...options: Options) => any
: (app: App, options: Options) => any
export type Plugin = export type Plugin<Options = any[]> =
| (PluginInstallFunction & { install?: PluginInstallFunction }) | (PluginInstallFunction<Options> & {
install?: PluginInstallFunction<Options>
})
| { | {
install: PluginInstallFunction install: PluginInstallFunction<Options>
} }
export function createAppContext(): AppContext { export function createAppContext(): AppContext {

95
test-dts/appUse.test-d.ts Normal file
View File

@ -0,0 +1,95 @@
import { createApp, App, Plugin } from './index'
const app = createApp({})
// Plugin without types accept anything
const PluginWithoutType: Plugin = {
install(app: App) {}
}
app.use(PluginWithoutType)
app.use(PluginWithoutType, 2)
app.use(PluginWithoutType, { anything: 'goes' }, true)
type PluginOptions = {
option1?: string
option2: number
option3: boolean
}
const PluginWithObjectOptions = {
install(app: App, options: PluginOptions) {
options.option1
options.option2
options.option3
}
}
for (const Plugin of [
PluginWithObjectOptions,
PluginWithObjectOptions.install
]) {
// @ts-expect-error: no params
app.use(Plugin)
// @ts-expect-error option2 and option3 (required) missing
app.use(Plugin, {})
// @ts-expect-error type mismatch
app.use(Plugin, undefined)
// valid options
app.use(Plugin, { option2: 1, option3: true })
app.use(Plugin, { option1: 'foo', option2: 1, option3: true })
}
const PluginNoOptions = {
install(app: App) {}
}
for (const Plugin of [PluginNoOptions, PluginNoOptions.install]) {
// no args
app.use(Plugin)
// @ts-expect-error unexpected plugin option
app.use(Plugin, {})
// @ts-expect-error only no options is valid
app.use(Plugin, undefined)
}
const PluginMultipleArgs = {
install: (app: App, a: string, b: number) => {}
}
for (const Plugin of [PluginMultipleArgs, PluginMultipleArgs.install]) {
// @ts-expect-error: 2 arguments expected
app.use(Plugin, 'hey')
app.use(Plugin, 'hey', 2)
}
const PluginOptionalOptions = {
install(
app: App,
options: PluginOptions = { option2: 2, option3: true, option1: 'foo' }
) {
options.option1
options.option2
options.option3
}
}
for (const Plugin of [PluginOptionalOptions, PluginOptionalOptions.install]) {
// both version are valid
app.use(Plugin)
app.use(Plugin, undefined)
// @ts-expect-error option2 and option3 (required) missing
app.use(Plugin, {})
// valid options
app.use(Plugin, { option2: 1, option3: true })
app.use(Plugin, { option1: 'foo', option2: 1, option3: true })
}
// still valid but it's better to use the regular function because this one can accept an optional param
const PluginTyped: Plugin<PluginOptions> = (app, options) => {}
// @ts-expect-error: needs options
app.use(PluginTyped)
app.use(PluginTyped, { option2: 2, option3: true })