From 25b9230f4d90ec5a742c36d905add8bd89dc6d4d Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Sun, 16 Aug 2026 17:05:35 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20ability=20to=20configure=20pl?= =?UTF-8?q?ugins=20globally=20(#7222)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description To date it was only possible to configure plugins in a local fashion. As such one was not capable of defining a plugin for all its properties at once. The newly added API called `installGlobalPlugin` should unlock that capability. ## Checklist — _Don't delete this checklist and make sure you do the following before opening the PR_ - [ ] I have a full understanding of every line in this PR — whether the code was hand-written, AI-generated, copied from external sources or produced by any other tool - [ ] I flagged the impact of my change (minor / patch / major) either by running `pnpm run bump` or by following the instructions from the changeset bot - [ ] I kept this PR focused on a single concern and did not bundle unrelated changes - [ ] I followed the [gitmoji](https://gitmoji.dev/) specification for the name of the PR, including the package scope (e.g. `🐛(vitest) Something...`) when the change targets a package other than `fast-check` - [ ] I added relevant tests and they would have failed without my PR (when applicable) --- .changeset/vast-mangos-end.md | 5 +++ .../fast-check/src/check/runner/Runner.ts | 39 +++++++++++++------ .../runner/configuration/GlobalPlugins.ts | 28 +++++++++++++ packages/fast-check/src/fast-check-default.ts | 2 + website/docs/advanced/plugins.md | 10 +++++ website/docs/configuration/global-settings.md | 4 ++ 6 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 .changeset/vast-mangos-end.md create mode 100644 packages/fast-check/src/check/runner/configuration/GlobalPlugins.ts diff --git a/.changeset/vast-mangos-end.md b/.changeset/vast-mangos-end.md new file mode 100644 index 00000000..e1ca8898 --- /dev/null +++ b/.changeset/vast-mangos-end.md @@ -0,0 +1,5 @@ +--- +"fast-check": minor +--- + +✨ Add ability to configure plugins globally diff --git a/packages/fast-check/src/check/runner/Runner.ts b/packages/fast-check/src/check/runner/Runner.ts index e8115e02..b704fbf1 100644 --- a/packages/fast-check/src/check/runner/Runner.ts +++ b/packages/fast-check/src/check/runner/Runner.ts @@ -18,6 +18,7 @@ import type { IAsyncProperty } from '../property/AsyncProperty.js'; import type { IProperty } from '../property/Property.js'; import type { Value } from '../arbitrary/definition/Value.js'; import type { Plugin, PluginInstance } from '../plugin/Plugin.js'; +import { readInstalledGlobalPlugins } from './configuration/GlobalPlugins.js'; /** @internal */ function runIt( @@ -65,6 +66,26 @@ async function asyncPropertyExecution(property: IRawProperty, v: Ts) { return out; } +function applyPlugins( + plugins: Plugin[], + pluginOffset: number, + crossPluginContext: { [K in any]?: unknown }, + run: IRawProperty['run'], + pluginAfterAllCallbacks: Required>['afterAll'][], +) { + let nextRun = run; + for (let index = 0; index !== plugins.length; ++index) { + const pluginInstance = plugins[index](pluginOffset, crossPluginContext); + if (pluginInstance.decorateRun !== undefined) { + nextRun = pluginInstance.decorateRun(nextRun); + } + if (pluginInstance.afterAll !== undefined) { + pluginAfterAllCallbacks.push(pluginInstance.afterAll.bind(pluginInstance)); + } + } + return nextRun; +} + /** * Run the property, do not throw contrary to {@link assert} * @@ -125,21 +146,15 @@ function check(rawProperty: IRawProperty, params?: Parameters): unkn throw new Error('Invalid parameters encountered, only asyncProperty can be used when asyncReporter specified'); const property = decorateProperty(rawProperty, qParams); - const pluginSharedSessionContext: { [K in any]?: unknown } = {}; - const plugins: Plugin[] = qParams.plugins; - const pluginAfterAllCallbacks: Required>['afterAll'][] = []; let run: typeof property.run = property.isAsync() ? async (v) => asyncPropertyExecution(property, v) : (v) => propertyExecution(property, v); - for (let index = 0; index !== plugins.length; ++index) { - const pluginInstance = plugins[index](index, pluginSharedSessionContext); - if (pluginInstance.decorateRun !== undefined) { - run = pluginInstance.decorateRun(run); - } - if (pluginInstance.afterAll !== undefined) { - pluginAfterAllCallbacks.push(pluginInstance.afterAll.bind(pluginInstance)); - } - } + const pluginAfterAllCallbacks: Required>['afterAll'][] = []; + + const crossPluginContext: { [K in any]?: unknown } = {}; + const globalPlugins = readInstalledGlobalPlugins(); + run = applyPlugins(globalPlugins, 0, crossPluginContext, run, pluginAfterAllCallbacks); + run = applyPlugins(qParams.plugins, globalPlugins.length, crossPluginContext, run, pluginAfterAllCallbacks); const maxInitialIterations = qParams.path.length === 0 || qParams.path.indexOf(':') === -1 ? qParams.numRuns : -1; const maxSkips = qParams.numRuns * qParams.maxSkipsPerRun; diff --git a/packages/fast-check/src/check/runner/configuration/GlobalPlugins.ts b/packages/fast-check/src/check/runner/configuration/GlobalPlugins.ts new file mode 100644 index 00000000..c488f899 --- /dev/null +++ b/packages/fast-check/src/check/runner/configuration/GlobalPlugins.ts @@ -0,0 +1,28 @@ +import type { Plugin } from '../../plugin/Plugin.js'; + +const globalPlugins: Plugin[] = []; + +/** + * Install a plugin to be used by all the runners + * Installed plugins come before the ones passed via the `plugins` option of the run. + * + * @example + * ```typescript + * fc.installGlobalPlugin(myPlugin()); + * //... + * fc.assert(myProp, { plugins: [myOtherPlugin()] }) + * // equivalent to { plugins: [myPlugin(), myOtherPlugin()] } + * ``` + * + * @param plugin - Plugin to be installed globally + * + * @remarks Since 4.10.0 + * @public + */ +export function installGlobalPlugin(plugin: Plugin): void { + globalPlugins.push(plugin); +} + +export function readInstalledGlobalPlugins(): Plugin[] { + return globalPlugins; +} diff --git a/packages/fast-check/src/fast-check-default.ts b/packages/fast-check/src/fast-check-default.ts index 93a77d74..0cda5d19 100644 --- a/packages/fast-check/src/fast-check-default.ts +++ b/packages/fast-check/src/fast-check-default.ts @@ -209,6 +209,7 @@ import { noBias } from './arbitrary/noBias.js'; import { limitShrink } from './arbitrary/limitShrink.js'; import type { RandomGenerator } from './random/generator/RandomGenerator.js'; import type { Plugin, PluginInstance } from './check/plugin/Plugin.js'; +import { installGlobalPlugin } from './check/runner/configuration/GlobalPlugins.js'; // Explicit cast into string to avoid to have __type: "process.env.__PACKAGE_TYPE__" /** @@ -452,6 +453,7 @@ export { configureGlobal, readConfigureGlobal, resetConfigureGlobal, + installGlobalPlugin, ExecutionStatus, Random, Stream, diff --git a/website/docs/advanced/plugins.md b/website/docs/advanced/plugins.md index 076d1728..6b90ab7b 100644 --- a/website/docs/advanced/plugins.md +++ b/website/docs/advanced/plugins.md @@ -25,3 +25,13 @@ await fc.assert(fc.asyncProperty(...arbs, predicate), { plugins: [pluginA(...paramsForPluginA), pluginB(...paramsForPluginB)], }); ``` + +## Installing plugins globally + +Plugins needed by all your properties can be installed once, typically in a [setup file](/docs/configuration/global-settings/#integration-with-test-frameworks), with `installGlobalPlugin`. + +```ts +fc.installGlobalPlugin(pluginA()); +``` + +Installed plugins run before the ones passed to the runner, so the snippet above followed by `fc.assert(myProp, { plugins: [pluginB()] })` is equivalent to `plugins: [pluginA(), pluginB()]`. diff --git a/website/docs/configuration/global-settings.md b/website/docs/configuration/global-settings.md index c375a900..063c3e5b 100644 --- a/website/docs/configuration/global-settings.md +++ b/website/docs/configuration/global-settings.md @@ -58,6 +58,10 @@ fc.configureGlobal({ ...fc.readConfigureGlobal(), ...myNewOptions }); You can also fully reset all the global options by calling `resetConfigureGlobal`. ::: +:::info Plugins +[Plugins](/docs/advanced/plugins/) cannot be shared via `configureGlobal`, they have their own installer: `fc.installGlobalPlugin(myPlugin())`. +::: + Resources: [API reference](/docs/api/functions/configureGlobal). Available since 1.18.0. -- 2.51.2