]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
types(runtime-core): support plugin options type inference (#3969)
authorTony Trinh <tony19@gmail.com>
Mon, 14 Nov 2022 01:13:32 +0000 (19:13 -0600)
committerGitHub <noreply@github.com>
Mon, 14 Nov 2022 01:13:32 +0000 (20:13 -0500)
packages/runtime-core/src/apiCreateApp.ts
test-dts/appUse.test-d.ts [new file with mode: 0644]

index 1c588dbc8f86fca5bb811c1e85f90706574629de..c02597bed584cb05bdddbd11a82a04701403fd15 100644 (file)
@@ -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 {
diff --git a/test-dts/appUse.test-d.ts b/test-dts/appUse.test-d.ts
new file mode 100644 (file)
index 0000000..526aa88
--- /dev/null
@@ -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 })