diff --git a/.changeset/moody-hairs-boil.md b/.changeset/moody-hairs-boil.md new file mode 100644 index 00000000..fdab3f6c --- /dev/null +++ b/.changeset/moody-hairs-boil.md @@ -0,0 +1,5 @@ +--- +"fast-check": minor +--- + +✨ Refine plugin API diff --git a/packages/fast-check/src/check/plugin/Plugin.ts b/packages/fast-check/src/check/plugin/Plugin.ts index 08d1ea06..e0ed38d3 100644 --- a/packages/fast-check/src/check/plugin/Plugin.ts +++ b/packages/fast-check/src/check/plugin/Plugin.ts @@ -12,14 +12,7 @@ import type { RunDetails } from '../runner/reporter/RunDetails.js'; * @remarks Since 4.10.0 * @public */ -export type PluginInstance = { - /** - * Whether or not the plugin can only be attached to asynchronous properties. - * - * @defaultValue false - * @remarks Since 4.10.0 - */ - asyncOnly?: IsAsync extends false ? false : boolean; +export type PluginInstance = { /** * Enrich the execution of the predicate linked to the property with extra behaviors. * Called once per execution of the predicate. @@ -29,7 +22,7 @@ export type PluginInstance = { * * @remarks Since 4.10.0 */ - decorateRun?: (nestedRun: IRawProperty['run']) => IRawProperty['run']; + decorateRun?: (nestedRun: IRawProperty['run']) => IRawProperty['run']; /** * Called once at the end of the full property assessment. * Gets called with the result of the execution. @@ -39,21 +32,21 @@ export type PluginInstance = { * * @remarks Since 4.10.0 */ - afterAll?: (runDetails: RunDetails) => IsAsync extends true ? Promise | void : void; + afterAll?: (runDetails: RunDetails) => Promise | void; }; /** * Builder instantiating a plugin. * Each property will instantiate its own plugin when starting to be assessed via {@link check} or {@link assert}. * - * NOTE: The function gets called with a parameter shared across all builders. - * The variable can be leveraged to exchange insights with other builders. As such it is writable and can be mutated via the builder. - * We recommend using symbol keys when adding entries to the variable to reduce the risk of collision with other unrelated plugins. + * Parameters received by the Plugin function: + * - 1st argument or pluginIndex: Corresponds to the index of the plugin (starts at zero). + * - 2nd argument or crossPluginContext: Context parameter shared across all builders. + * The variable can be leveraged to exchange insights with other builders. + * As such it is writable and can be mutated via the builder. + * We recommend using symbol keys when adding entries to the variable to reduce the risk of collision with other unrelated plugins. * * @remarks Since 4.10.0 * @public */ -export type Plugin = (crossPluginContext: { [K in any]?: unknown }) => PluginInstance< - Ts, - IsAsync ->; +export type Plugin = (pluginIndex: number, crossPluginContext: { [K in any]?: unknown }) => PluginInstance; diff --git a/packages/fast-check/src/check/runner/Runner.ts b/packages/fast-check/src/check/runner/Runner.ts index 15bf5764..e8115e02 100644 --- a/packages/fast-check/src/check/runner/Runner.ts +++ b/packages/fast-check/src/check/runner/Runner.ts @@ -126,16 +126,13 @@ function check(rawProperty: IRawProperty, params?: Parameters): unkn const property = decorateProperty(rawProperty, qParams); const pluginSharedSessionContext: { [K in any]?: unknown } = {}; - const plugins: Plugin[] = qParams.plugins; - const pluginAfterAllCallbacks: Required>['afterAll'][] = []; + 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](pluginSharedSessionContext); - if (pluginInstance.asyncOnly && !property.isAsync()) { - throw new Error('Cannot execute an asynchronous plugin on a synchronous property'); - } + const pluginInstance = plugins[index](index, pluginSharedSessionContext); if (pluginInstance.decorateRun !== undefined) { run = pluginInstance.decorateRun(run); } diff --git a/packages/fast-check/src/check/runner/configuration/Parameters.ts b/packages/fast-check/src/check/runner/configuration/Parameters.ts index 88e78e81..cc16c25b 100644 --- a/packages/fast-check/src/check/runner/configuration/Parameters.ts +++ b/packages/fast-check/src/check/runner/configuration/Parameters.ts @@ -212,5 +212,5 @@ export interface Parameters { * * @remarks Since 4.10.0 */ - plugins?: Plugin[]; + plugins?: Plugin[]; } diff --git a/packages/fast-check/src/check/runner/configuration/QualifiedParameters.ts b/packages/fast-check/src/check/runner/configuration/QualifiedParameters.ts index 54069cf9..b328c7e8 100644 --- a/packages/fast-check/src/check/runner/configuration/QualifiedParameters.ts +++ b/packages/fast-check/src/check/runner/configuration/QualifiedParameters.ts @@ -44,7 +44,7 @@ export class QualifiedParameters { reporter: ((runDetails: RunDetails) => void) | undefined; asyncReporter: ((runDetails: RunDetails) => Promise) | undefined; includeErrorInReport: boolean; - plugins: Plugin[]; + plugins: Plugin[]; constructor(op?: Parameters) { const p = op || {}; diff --git a/packages/fast-check/test/e2e/Plugins.spec.ts b/packages/fast-check/test/e2e/Plugins.spec.ts index dc5ffa23..da360984 100644 --- a/packages/fast-check/test/e2e/Plugins.spec.ts +++ b/packages/fast-check/test/e2e/Plugins.spec.ts @@ -6,7 +6,7 @@ describe(`Plugins (seed: ${seed})`, () => { it('should wait and queue afterAll', async () => { // Arrange const probes: string[] = []; - const buildPlugin = (pluginName: string): fc.Plugin<[number], true> => { + const buildPlugin = (pluginName: string): fc.Plugin<[number]> => { return () => { probes.push(`${pluginName} instantiated`); return { @@ -45,7 +45,7 @@ describe(`Plugins (seed: ${seed})`, () => { it('should stack decorateRun with the first plugin being the closest to the predicate', () => { // Arrange const probes: string[] = []; - const buildPlugin = (pluginName: string): fc.Plugin<[number], false> => { + const buildPlugin = (pluginName: string): fc.Plugin<[number]> => { return () => { probes.push(`${pluginName} instantiated`); return { @@ -94,7 +94,7 @@ describe(`Plugins (seed: ${seed})`, () => { it('should await decorateRun of asynchronous plugins', async () => { // Arrange const probes: string[] = []; - const buildPlugin = (pluginName: string): fc.Plugin<[number], true> => { + const buildPlugin = (pluginName: string): fc.Plugin<[number]> => { return () => { return { asyncOnly: true, @@ -137,7 +137,7 @@ describe(`Plugins (seed: ${seed})`, () => { it('should support mixes of sync and async afterAll', async () => { // Arrange const probes: string[] = []; - const buildPlugin = (pluginName: string, isAsync: boolean): fc.Plugin<[number], true> => { + const buildPlugin = (pluginName: string, isAsync: boolean): fc.Plugin<[number]> => { return () => { probes.push(`${pluginName} instantiated`); return { diff --git a/packages/fast-check/test/unit/check/runner/configuration/QualifiedParameters.spec.ts b/packages/fast-check/test/unit/check/runner/configuration/QualifiedParameters.spec.ts index 10caeed5..1717f8aa 100644 --- a/packages/fast-check/test/unit/check/runner/configuration/QualifiedParameters.spec.ts +++ b/packages/fast-check/test/unit/check/runner/configuration/QualifiedParameters.spec.ts @@ -32,7 +32,7 @@ const parametersArbitrary = fc.record( reporter: fc.func(fc.constant(undefined)), asyncReporter: fc.func(fc.constant(Promise.resolve(undefined))), includeErrorInReport: fc.boolean(), - plugins: fc.constant([] satisfies Plugin[]), + plugins: fc.constant([] satisfies Plugin[]), }, { requiredKeys: [] }, );