Преглед на файлове

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

Tony Trinh преди 3 години
родител
ревизия
c513126c5d
променени са 2 файла, в които са добавени 110 реда и са изтрити 5 реда
  1. 15 5
      packages/runtime-core/src/apiCreateApp.ts
  2. 95 0
      test-dts/appUse.test-d.ts

+ 15 - 5
packages/runtime-core/src/apiCreateApp.ts

@@ -31,7 +31,13 @@ import { ObjectEmitsOptions } from './componentEmits'
 export interface App<HostElement = any> {
   version: string
   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
   component(name: string): Component | undefined
   component(name: string, component: Component): this
@@ -140,12 +146,16 @@ export interface AppContext {
   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 =
-  | (PluginInstallFunction & { install?: PluginInstallFunction })
+export type Plugin<Options = any[]> =
+  | (PluginInstallFunction<Options> & {
+      install?: PluginInstallFunction<Options>
+    })
   | {
-      install: PluginInstallFunction
+      install: PluginInstallFunction<Options>
     }
 
 export function createAppContext(): AppContext {

+ 95 - 0
test-dts/appUse.test-d.ts

@@ -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 })