From 807ffb97929aa57b1036d27e31413d3ee5670767 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Sat, 12 Aug 2023 16:59:26 +0200 Subject: [PATCH] Add improved types * Add better support for different compile results, if you have custom results, add them to `CompileResultMap` * Better input/output of functions * Infer plugins better * Redo all docs --- index.d.ts | 1162 +++++++++++++++++++++++-------------- index.test-d.ts | 262 ++++----- lib/index.js | 48 +- package.json | 7 +- readme.md | 41 +- test/freeze.js | 52 +- test/process-compilers.js | 11 +- test/process-sync.js | 45 +- test/process.js | 12 +- test/run-sync.js | 14 +- test/run.js | 42 +- test/stringify.js | 1 + test/use.js | 18 +- test/util/simple.js | 38 +- 14 files changed, 1016 insertions(+), 737 deletions(-) diff --git a/index.d.ts b/index.d.ts index 20c00835..cf53d2af 100644 --- a/index.d.ts +++ b/index.d.ts @@ -14,127 +14,258 @@ // accept type parameters cannot be re-exported as such easily. import type {Node} from 'unist' -import type {VFile, VFileCompatible} from 'vfile' +import type {VFile, VFileCompatible, VFileValue} from 'vfile' -type VFileWithOutput = Result extends Uint8Array - ? VFile - : Result extends object // Custom result type - ? VFile & {result: Result} - : VFile +/** + * Interface of known results from compilers. + * + * Normally, compilers result in text ({@link VFileValue `VFileValue`}). + * When you compile to something else, such as a React node (as in, + * `rehype-react`), you can augment this interface to include that type. + * + * ```ts + * import type {ReactNode} from 'somewhere' + * + * declare module 'unified' { + * interface CompileResultMap { + * // Register a new result (value is used, key should match it). + * ReactNode: ReactNode + * } + * } + * ``` + * + * Use {@link CompileResults `CompileResults`} to access the values. + */ +// Note: if `Value` from `VFile` is changed, this should too. +export interface CompileResultMap { + Uint8Array: Uint8Array + string: string +} -// Get the right most non-void thing. -type Specific = Right extends undefined | void - ? Left - : Right +/** + * Acceptable results from compilers. + * + * To register custom results, add them to + * {@link CompileResultMap `CompileResultMap`}. + */ +type CompileResults = CompileResultMap[keyof CompileResultMap] -// Create a processor based on the input/output of a plugin. +/** + * Type to generate a {@link VFile `VFile`} corresponding to a compiler result. + * + * If a result that is not acceptable on a `VFile` is used, that will + * be stored on the `result` field of {@link VFile `VFile`}. + * + * @typeParam Result + * Compile result. + */ +type VFileWithOutput = + Result extends VFileValue | undefined ? VFile : VFile & {result: Result} + +/** + * Create a processor based on the input/output of a {@link Plugin plugin}. + * + * @typeParam ParseTree + * Output of `parse`. + * @typeParam HeadTree + * Input for `run`. + * @typeParam TailTree + * Output for `run`. + * @typeParam CompileTree + * Input of `stringify`. + * @typeParam CompileResult + * Output of `stringify`. + * @typeParam Input + * Input of plugin. + * @typeParam Output + * Output of plugin. + */ type UsePlugin< - ParseTree extends Node | void = void, - CurrentTree extends Node | void = void, - CompileTree extends Node | void = void, - CompileResult = void, - Input = void, - Output = void -> = Output extends Node - ? Input extends string - ? // If `Input` is `string` and `Output` is `Node`, then this plugin - // defines a parser, so set `ParseTree`. + ParseTree extends Node | undefined, + HeadTree extends Node | undefined, + TailTree extends Node | undefined, + CompileTree extends Node | undefined, + CompileResult extends CompileResults | undefined, + Input extends Node | string | undefined, + Output +> = Input extends string + ? Output extends Node | undefined + ? // Parser. Processor< - Output, - Specific, - Specific, + Output extends undefined ? ParseTree : Output, + HeadTree, + TailTree, + CompileTree, CompileResult > - : Input extends Node - ? // If `Input` is `Node` and `Output` is `Node`, then this plugin defines a - // transformer, its output defines the input of the next, so set - // `CurrentTree`. + : // Unknown. + Processor + : Output extends CompileResults + ? Input extends Node | undefined + ? // Compiler. + Processor< + ParseTree, + HeadTree, + TailTree, + Input extends undefined ? CompileTree : Input, + Output extends undefined ? CompileResult : Output + > + : // Unknown. + Processor + : Input extends Node | undefined + ? Output extends Node | undefined + ? // Transform. Processor< - Specific, - Output, - Specific, + ParseTree, + // No `HeadTree` yet? Set `Input`. + HeadTree extends undefined ? Input : HeadTree, + Output extends undefined ? TailTree : Output, + CompileTree, CompileResult > - : // Else, `Input` is something else and `Output` is `Node`: - never - : Input extends Node - ? // If `Input` is `Node` and `Output` is not a `Node`, then this plugin - // defines a compiler, so set `CompileTree` and `CompileResult` - Processor< - Specific, - Specific, - Input, - Output - > - : // Else, `Input` is not a `Node` and `Output` is not a `Node`. - // Maybe it’s untyped, or the plugin throws an error (`never`), so lets - // just keep it as it was. - Processor + : // Unknown. + Processor + : // Unknown. + Processor /** - * Processor allows plugins to be chained together to transform content. - * The chain of plugins defines how content flows through it. + * Processor. * * @typeParam ParseTree - * The node that the parser yields (and `run` receives). - * @typeParam CurrentTree - * The node that the last attached plugin yields. + * Output of `parse`. + * @typeParam HeadTree + * Input for `run`. + * @typeParam TailTree + * Output for `run`. * @typeParam CompileTree - * The node that the compiler receives (and `run` yields). + * Input of `stringify`. * @typeParam CompileResult - * The thing that the compiler yields. + * Output of `stringify`. */ export type Processor< - ParseTree extends Node | void = void, - CurrentTree extends Node | void = void, - CompileTree extends Node | void = void, - CompileResult = void + ParseTree extends Node | undefined = undefined, + HeadTree extends Node | undefined = undefined, + TailTree extends Node | undefined = undefined, + CompileTree extends Node | undefined = undefined, + CompileResult extends CompileResults | undefined = undefined > = { /** - * Configure the processor to use a plugin. + * Configure the processor with a preset. + * + * If the processor is already using a plugin, the previous plugin + * configuration is changed based on the options that are passed in. + * In other words, the plugin is not added a second time. + * + * @example + * ```js + * import {unified} from 'unified' * - * @typeParam PluginParameters - * Plugin settings. + * unified() + * // Preset with plugins and settings: + * .use({plugins: [pluginA, [pluginB, {}]], settings: {position: false}}) + * // Settings only: + * .use({settings: {position: false}}) + * ``` + * + * @param preset + * Single preset ({@link Preset `Preset`}): an object with a `plugins` + * and/or `settings`. + * @returns + * Current processor. + */ + use( + preset?: Preset | null | undefined + ): Processor + + /** + * Configure the processor with a list of usable values. + * + * If the processor is already using a plugin, the previous plugin + * configuration is changed based on the options that are passed in. + * In other words, the plugin is not added a second time. + * + * @example + * ```js + * import {unified} from 'unified' + * + * unified() + * // Plugins: + * .use([pluginA, pluginB]) + * // Two plugins, the second with options: + * .use([pluginC, [pluginD, {}]]) + * ``` + * + * @param list + * List of plugins plugins, presets, and tuples + * ({@link PluggableList `PluggableList`}). + * @returns + * Current processor. + */ + use( + list: PluggableList + ): Processor + + /** + * Configure the processor to use a {@link Plugin `Plugin`}. + * + * If the processor is already using a plugin, the previous plugin + * configuration is changed based on the options that are passed in. + * In other words, the plugin is not added a second time. + * + * @example + * ```js + * import {unified} from 'unified' + * + * unified() + * // Plugin with options: + * .use(pluginA, {x: true, y: true}) + * // Passing the same plugin again merges configuration (to `{x: true, y: false, z: true}`): + * .use(pluginA, {y: false, z: true}) + * ``` + * + * @typeParam Parameters + * Arguments passed to the plugin. * @typeParam Input - * Value that is accepted by the plugin. + * Value that is expected as input. * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer expects. - * * If the plugin sets a parser, then this should be `string`. - * * If the plugin sets a compiler, then this should be the node type that - * the compiler expects. + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node it expects. + * * If the plugin sets a {@link Parser `Parser`}, this should be + * `string`. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be the + * node it expects. * @typeParam Output - * Value that the plugin yields. - * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer yields, and defaults to `Input`. - * * If the plugin sets a parser, then this should be the node type that - * the parser yields. - * * If the plugin sets a compiler, then this should be the result that - * the compiler yields (`string`, `Uint8Array`, or something else). + * Value that is yielded as output. + * + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node that that yields. + * * If the plugin sets a {@link Parser `Parser`}, this should be the + * node that it yields. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be + * result it yields. * @param plugin - * Plugin (function) to use. - * Plugins are deduped based on identity: passing a function in twice will - * cause it to run only once. - * @param settings - * Configuration for plugin, optional. + * {@link Plugin `Plugin`} to use. + * @param parameters + * Arguments passed to the {@link Plugin plugin}. + * * Plugins typically receive one options object, but could receive other and * more values. - * It’s also possible to pass a boolean instead of settings: `true` (to turn - * a plugin on) or `false` (to turn a plugin off). + * It’s also possible to pass a boolean: `true` (to turn a plugin on), + * `false` (to turn a plugin off). * @returns * Current processor. */ use< - PluginParameters extends any[] = any[], - Input = Specific, + Parameters extends unknown[] = [], + Input extends Node | string | undefined = undefined, Output = Input >( - plugin: Plugin, - ...settings: PluginParameters | [boolean] + plugin: Plugin, + ...parameters: Parameters | [boolean] ): UsePlugin< ParseTree, - CurrentTree, + HeadTree, + TailTree, CompileTree, CompileResult, Input, @@ -142,299 +273,427 @@ export type Processor< > /** - * Configure the processor with a tuple of a plugin and setting(s). + * Configure the processor to use a tuple of a {@link Plugin `Plugin`} with + * its parameters. * - * @typeParam PluginParameters - * Plugin settings. + * If the processor is already using a plugin, the previous plugin + * configuration is changed based on the options that are passed in. + * In other words, the plugin is not added a second time. + * + * @example + * ```js + * import {unified} from 'unified' + * + * unified() + * // Plugin with options: + * .use([pluginA, {x: true, y: true}]) + * ``` + * + * @typeParam Parameters + * Arguments passed to the plugin. * @typeParam Input - * Value that is accepted by the plugin. + * Value that is expected as input. * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer expects. - * * If the plugin sets a parser, then this should be `string`. - * * If the plugin sets a compiler, then this should be the node type that - * the compiler expects. + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node it expects. + * * If the plugin sets a {@link Parser `Parser`}, this should be + * `string`. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be the + * node it expects. * @typeParam Output - * Value that the plugin yields. - * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer yields, and defaults to `Input`. - * * If the plugin sets a parser, then this should be the node type that - * the parser yields. - * * If the plugin sets a compiler, then this should be the result that - * the compiler yields (`string`, `Uint8Array`, or something else). + * Value that is yielded as output. + * + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node that that yields. + * * If the plugin sets a {@link Parser `Parser`}, this should be the + * node that it yields. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be + * result it yields. * @param tuple - * A tuple where the first item is a plugin (function) to use and other - * items are options. - * Plugins are deduped based on identity: passing a function in twice will - * cause it to run only once. - * It’s also possible to pass a boolean instead of settings: `true` (to turn - * a plugin on) or `false` (to turn a plugin off). + * {@link Plugin `Plugin`} with arguments to use. + * + * Plugins typically receive one options object, but could receive other and + * more values. + * It’s also possible to pass a boolean: `true` (to turn a plugin on), + * `false` (to turn a plugin off). * @returns * Current processor. */ use< - PluginParameters extends any[] = any[], - Input = Specific, + Parameters extends unknown[] = [], + Input extends Node | string | undefined = undefined, Output = Input >( tuple: - | PluginTuple - | [Plugin, boolean] + | [plugin: Plugin, enable: boolean] // Enable or disable the plugin. + | [plugin: Plugin, ...parameters: Parameters] // Configure the plugin. ): UsePlugin< ParseTree, - CurrentTree, + HeadTree, + TailTree, CompileTree, CompileResult, Input, Output > - - /** - * Configure the processor with a preset or list of plugins and presets. - * - * @param presetOrList - * Either a list of plugins, presets, and tuples, or a single preset: an - * object with a `plugins` (list) and/or `settings` - * (`Record`). - * @returns - * Current processor. - */ - use( - presetOrList: PluggableList | Preset - ): Processor -} & FrozenProcessor +} & FrozenProcessor /** - * A frozen processor is just like a regular processor, except no additional - * plugins can be added. - * A frozen processor can be created by calling `.freeze()` on a processor. - * An unfrozen processor can be created by calling a processor. + * Frozen processor. + * + * @typeParam ParseTree + * Output of `parse`. + * @typeParam HeadTree + * Input for `run`. + * @typeParam TailTree + * Output for `run`. + * @typeParam CompileTree + * Input of `stringify`. + * @typeParam CompileResult + * Output of `stringify`. */ export type FrozenProcessor< - ParseTree extends Node | void = void, - CurrentTree extends Node | void = void, - CompileTree extends Node | void = void, - CompileResult = void + ParseTree extends Node | undefined = undefined, + HeadTree extends Node | undefined = undefined, + TailTree extends Node | undefined = undefined, + CompileTree extends Node | undefined = undefined, + CompileResult extends CompileResults | undefined = undefined > = { /** - * Clone current processor + * Create a processor. * * @returns - * New unfrozen processor that is configured to function the same as its - * ancestor. - * But when the descendant processor is configured it does not affect the - * ancestral processor. + * New *unfrozen* processor ({@link Processor `Processor`}) that is + * configured to work the same as its ancestor. + * When the descendant processor is configured in the future it does not + * affect the ancestral processor. */ - (): Processor + (): Processor /** * Internal list of configured plugins. * * @private */ - attachers: Array<[Plugin, ...unknown[]]> + attachers: Array> - Parser?: Parser> | undefined + /** + * A **parser** handles the parsing of text to a syntax tree. + * + * It is used in the parse phase and is called with a `string` and + * {@link VFile `VFile`} of the document to parse. + * + * `Parser` can be a normal function, in which case it must return the syntax + * tree representation of the given file ({@link Node `Node`}). + * + * `Parser` can also be a constructor function (a function with a `parse` + * field in its `prototype`), in which case it is constructed with `new`. + * Instances must have a `parse` method that is called without arguments and must + * return a {@link Node `Node`}. + */ + Parser?: Parser | undefined + /** + * A **compiler** handles the compiling of a syntax tree to something else (in + * most cases, text). + * + * It is used in the stringify phase and called with a {@link Node `Node`} + * and {@link VFile `VFile`} representation of the document to compile. + * + * `Compiler` can be a normal function, in which case it should return the + * textual representation of the given tree (`string`). + * + * `Compiler` can also be a constructor function (a function with a `compile` + * field in its `prototype`), in which case it is constructed with `new`. + * Instances must have a `compile` method that is called without arguments and + * should return a `string`. + * + * > 👉 **Note**: unified typically compiles by serializing: most compilers + * > return `string` (or `Uint8Array`). + * > Some compilers, such as the one configured with + * > [`rehype-react`][rehype-react], return other values (in this case, a + * > React tree). + * > If you’re using a compiler that doesn’t serialize, expect different result + * > values. + * > + * > To register custom results in TypeScript, add them to + * > {@link CompileResultMap `CompileResultMap`}. + * + * [rehype-react]: https://github.com/rehypejs/rehype-react + */ Compiler?: - | Compiler, Specific> + | Compiler< + CompileTree extends undefined ? Node : CompileTree, + CompileResult extends undefined ? unknown : CompileResult + > | undefined /** - * Parse a file. + * Parse text to a syntax tree. + * + * > 👉 **Note**: `parse` freezes the processor if not already *frozen*. + * + * > 👉 **Note**: `parse` performs the parse phase, not the run phase or other + * > phases. * * @param file - * File to parse. - * `VFile` or anything that can be given to `new VFile()`, optional. + * file to parse; typically `string`; any value accepted as `x` in + * `new VFile(x)`. * @returns - * Resulting tree. + * Syntax tree representing `file`. */ - parse(file?: VFileCompatible | undefined): Specific + parse( + file?: VFileCompatible | undefined + ): ParseTree extends undefined ? Node : ParseTree /** - * Compile a file. + * Compile a syntax tree. + * + * > 👉 **Note**: `stringify` freezes the processor if not already *frozen*. + * + * > 👉 **Note**: `stringify` performs the stringify phase, not the run phase + * or other phases. * - * @param node - * Node to compile. + * @param tree + * Tree to compile * @param file - * `VFile` or anything that can be given to `new VFile()`, optional. + * File associated with `node` (optional); any value accepted as `x` in + * `new VFile(x)`. * @returns - * New content: compiled text (`string` or `Uint8Array`) or something else. - * This depends on which plugins you use: typically text, but could for - * example be a React node. + * Textual representation of the tree (see note). + * + * > 👉 **Note**: unified typically compiles by serializing: most compilers + * > return `string` (or `Uint8Array`). + * > Some compilers, such as the one configured with + * > [`rehype-react`][rehype-react], return other values (in this case, a + * > React tree). + * > If you’re using a compiler that doesn’t serialize, expect different result + * > values. + * > + * > To register custom results in TypeScript, add them to + * > {@link CompileResultMap `CompileResultMap`}. + * + * [rehype-react]: https://github.com/rehypejs/rehype-react */ stringify( - node: Specific, + tree: CompileTree extends undefined ? Node : CompileTree, file?: VFileCompatible | undefined - ): CompileTree extends Node ? CompileResult : unknown + ): CompileResult extends undefined ? VFileValue : CompileResult /** - * Run transforms on the given tree. + * Run *transformers* on a syntax tree. + * + * > 👉 **Note**: `run` freezes the processor if not already *frozen*. + * + * > 👉 **Note**: `run` performs the run phase, not other phases. * - * @param node - * Tree to transform. - * @param callback - * Callback called with an error or the resulting node. + * @param tree + * Tree to transform and inspect. + * @param done + * Callback. * @returns * Nothing. */ run( - node: Specific, - callback: RunCallback> - ): void + tree: HeadTree extends undefined ? Node : HeadTree, + done: RunCallback + ): undefined /** - * Run transforms on the given node. + * Run *transformers* on a syntax tree. + * + * > 👉 **Note**: `run` freezes the processor if not already *frozen*. + * + * > 👉 **Note**: `run` performs the run phase, not other phases. * - * @param node - * Tree to transform. + * @param tree + * Tree to transform and inspect. * @param file - * File associated with `node`. - * `VFile` or anything that can be given to `new VFile()`. - * @param callback - * Callback called with an error or the resulting node. + * File associated with `node` (optional); any value accepted as `x` in + * `new VFile(x)`. + * @param done + * Callback. * @returns * Nothing. */ run( - node: Specific, + tree: HeadTree extends undefined ? Node : HeadTree, file: VFileCompatible | undefined, - callback: RunCallback> - ): void + done: RunCallback + ): undefined /** - * Run transforms on the given node. + * Run *transformers* on a syntax tree. + * + * > 👉 **Note**: `run` freezes the processor if not already *frozen*. * - * @param node - * Tree to transform. + * > 👉 **Note**: `run` performs the run phase, not other phases. + * + * @param tree + * Tree to transform and inspect. * @param file - * File associated with `node`. - * `VFile` or anything that can be given to `new VFile()`. + * File associated with `node` (optional); any value accepted as `x` in + * `new VFile(x)`. * @returns - * Promise that resolves to the resulting tree. + * A `Promise` rejected with a fatal error or resolved with the transformed + * tree. */ run( - node: Specific, + tree: HeadTree extends undefined ? Node : HeadTree, file?: VFileCompatible | undefined - ): Promise> + ): Promise /** - * Run transforms on the given node, synchronously. - * Throws when asynchronous transforms are configured. + * Run *transformers* on a syntax tree. + * + * An error is thrown if asynchronous transforms are configured. + * + * > 👉 **Note**: `runSync` freezes the processor if not already *frozen*. + * + * > 👉 **Note**: `runSync` performs the run phase, not other phases. * - * @param node - * Tree to transform. + * @param tree + * Tree to transform and inspect. * @param file - * File associated with `node`. - * `VFile` or anything that can be given to `new VFile()`, optional. + * File associated with `node` (optional); any value accepted as `x` in + * `new VFile(x)`. * @returns - * Resulting tree. + * Transformed tree. */ runSync( - node: Specific, + tree: HeadTree extends undefined ? Node : HeadTree, file?: VFileCompatible | undefined - ): Specific + ): TailTree extends undefined ? Node : TailTree /** - * Process a file. - * - * This performs all phases of the processor: + * Process the given file as configured on the processor. * - * 1. Parse a file into a unist node using the configured `Parser` - * 2. Run transforms on that node - * 3. Compile the resulting node using the `Compiler` + * > 👉 **Note**: `process` freezes the processor if not already *frozen*. * - * The result from the compiler is stored on the file. - * What the result is depends on which plugins you use. - * The result is typically text (`string` or `Uint8Array`), which can be - * retrieved with `file.toString()` (or `String(file)`). - * In some cases, such as when using `rehypeReact` to create a React node, - * the result is stored on `file.result`. + * > 👉 **Note**: `process` performs the parse, run, and stringify phases. * * @param file - * `VFile` or anything that can be given to `new VFile()`. - * @param callback - * Callback called with an error or the resulting file. + * File; any value accepted as `x` in `new VFile(x)`. + * @param done + * Callback. * @returns * Nothing. */ process( file: VFileCompatible | undefined, - callback: ProcessCallback> - ): void + done: ProcessCallback> + ): undefined /** - * Process a file. - * - * This performs all phases of the processor: + * Process the given file as configured on the processor. * - * 1. Parse a file into a unist node using the configured `Parser` - * 2. Run transforms on that node - * 3. Compile the resulting node using the `Compiler` + * > 👉 **Note**: `process` freezes the processor if not already *frozen*. * - * The result from the compiler is stored on the file. - * What the result is depends on which plugins you use. - * The result is typically text (`string` or `Uint8Array`), which can be - * retrieved with `file.toString()` (or `String(file)`). - * In some cases, such as when using `rehypeReact` to create a React node, - * the result is stored on `file.result`. + * > 👉 **Note**: `process` performs the parse, run, and stringify phases. * * @param file - * `VFile` or anything that can be given to `new VFile()`. + * File; any value accepted as `x` in `new VFile(x)`. * @returns - * Promise that resolves to the resulting `VFile`. + * `Promise` rejected with a fatal error or resolved with the processed + * file. + * + * The parsed, transformed, and compiled value is available at + * `file.value` (see note). + * + * > 👉 **Note**: unified typically compiles by serializing: most + * > compilers return `string` (or `Uint8Array`). + * > Some compilers, such as the one configured with + * > [`rehype-react`][rehype-react], return other values (in this case, a + * > React tree). + * > If you’re using a compiler that doesn’t serialize, expect different result + * > values. + * > + * > To register custom results in TypeScript, add them to + * > {@link CompileResultMap `CompileResultMap`}. + * + * [rehype-react]: https://github.com/rehypejs/rehype-react */ - process(file: VFileCompatible): Promise> + process( + file?: VFileCompatible | undefined + ): Promise> /** - * Process a file, synchronously. - * Throws when asynchronous transforms are configured. + * Process the given file as configured on the processor. * - * This performs all phases of the processor: + * An error is thrown if asynchronous transforms are configured. * - * 1. Parse a file into a unist node using the configured `Parser` - * 2. Run transforms on that node - * 3. Compile the resulting node using the `Compiler` + * > 👉 **Note**: `processSync` freezes the processor if not already *frozen*. * - * The result from the compiler is stored on the file. - * What the result is depends on which plugins you use. - * The result is typically text (`string` or `Uint8Array`), which can be - * retrieved with `file.toString()` (or `String(file)`). - * In some cases, such as when using `rehypeReact` to create a React node, - * the result is stored on `file.result`. + * > 👉 **Note**: `processSync` performs the parse, run, and stringify phases. * * @param file - * `VFile` or anything that can be given to `new VFile()`, optional. + * File; any value accepted as `x` in `new VFile(x)`. * @returns - * Resulting file. + * The processed file. + * + * The parsed, transformed, and compiled value is available at + * `file.value` (see note). + * + * > 👉 **Note**: unified typically compiles by serializing: most + * > compilers return `string` (or `Uint8Array`). + * > Some compilers, such as the one configured with + * > [`rehype-react`][rehype-react], return other values (in this case, a + * > React tree). + * > If you’re using a compiler that doesn’t serialize, expect different result + * > values. + * > + * > To register custom results in TypeScript, add them to + * > {@link CompileResultMap `CompileResultMap`}. + * + * [rehype-react]: https://github.com/rehypejs/rehype-react */ processSync( file?: VFileCompatible | undefined ): VFileWithOutput /** - * Get an in-memory key-value store accessible to all phases of the process. + * Configure the processor with info available to all plugins. + * Information is stored in an object. + * + * Typically, options can be given to a specific plugin, but sometimes it + * makes sense to have information shared with several plugins. + * For example, a list of HTML elements that are self-closing, which is + * needed during all phases. * * @returns - * Key-value store. + * The key-value store. */ data(): Record /** - * Set an in-memory key-value store accessible to all phases of the process. + * Configure the processor with info available to all plugins. + * Information is stored in an object. + * + * Typically, options can be given to a specific plugin, but sometimes it + * makes sense to have information shared with several plugins. + * For example, a list of HTML elements that are self-closing, which is + * needed during all phases. + * + * > 👉 **Note**: setting information cannot occur on *frozen* processors. + * > Call the processor first to create a new unfrozen processor. * * @param data - * Key-value store. + * Values to set. * @returns - * Current processor. + * The processor that `data` is called on. */ data( data: Record - ): Processor + ): Processor /** - * Get an in-memory value by key. + * Configure the processor with info available to all plugins. + * Information is stored in an object. + * + * Typically, options can be given to a specific plugin, but sometimes it + * makes sense to have information shared with several plugins. + * For example, a list of HTML elements that are self-closing, which is + * needed during all phases. * * @param key * Key to get. @@ -444,159 +703,185 @@ export type FrozenProcessor< data(key: string): unknown /** - * Set an in-memory value by key. + * Configure the processor with info available to all plugins. + * Information is stored in an object. + * + * Typically, options can be given to a specific plugin, but sometimes it + * makes sense to have information shared with several plugins. + * For example, a list of HTML elements that are self-closing, which is + * needed during all phases. + * + * > 👉 **Note**: setting information cannot occur on *frozen* processors. + * > Call the processor first to create a new unfrozen processor. * * @param key * Key to set. * @param value * Value to set. * @returns - * Current processor. + * The processor that `data` is called on. */ data( key: string, value: unknown - ): Processor + ): Processor /** * Freeze a processor. - * Frozen processors are meant to be extended and not to be configured or - * processed directly. * - * Once a processor is frozen it cannot be unfrozen. - * New processors working just like it can be created by calling the + * Frozen processors are meant to be extended and not to be configured + * directly. + * + * When a processor is frozen it cannot be unfrozen. + * New processors working the same way can be created by calling the * processor. * - * It’s possible to freeze processors explicitly, by calling `.freeze()`, but - * `.parse()`, `.run()`, `.stringify()`, and `.process()` call `.freeze()` to - * freeze a processor too. + * It’s possible to freeze processors explicitly by calling `.freeze()`. + * Processors freeze automatically when `.parse()`, `.run()`, `.runSync()`, + * `.stringify()`, `.process()`, or `.processSync()` are called. + * * * @returns - * Frozen processor. + * The processor that `freeze` was called on. */ - freeze(): FrozenProcessor + freeze(): FrozenProcessor< + ParseTree, + HeadTree, + TailTree, + CompileTree, + CompileResult + > } /** - * A plugin is a function. - * It configures the processor and in turn can receive options. - * Plugins can configure processors by interacting with parsers and compilers - * (at `this.Parser` or `this.Compiler`) or by specifying how the syntax tree - * is handled (by returning a `Transformer`). - * - * @typeParam PluginParameters - * Plugin settings. + * **Plugins** configure the processors they are applied on in the following + * ways: + * + * * they change the processor, such as the parser, the compiler, or by + * configuring data + * * they specify how to handle trees and files + * + * Plugins are a concept. + * They materialize as `Attacher`s. + * + * Attachers are materialized plugins. + * They are functions that can receive options and configure the processor. + * + * Attachers change the processor, such as the parser, the compiler, by + * configuring data, or by specifying how the tree and file are handled. + * + * > 👉 **Note**: attachers are called when the processor is *frozen*, + * > not when they are applied. + * + * @typeParam Parameters + * Arguments passed to the plugin. * @typeParam Input - * Value that is accepted by the plugin. + * Value that is expected as input. * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer expects. - * * If the plugin sets a parser, then this should be `string`. - * * If the plugin sets a compiler, then this should be the node type that - * the compiler expects. + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node it expects. + * * If the plugin sets a {@link Parser `Parser`}, this should be + * `string`. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be the + * node it expects. * @typeParam Output - * Value that the plugin yields. - * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer yields, and defaults to `Input`. - * * If the plugin sets a parser, then this should be the node type that - * the parser yields. - * * If the plugin sets a compiler, then this should be the result that - * the compiler yields (`string`, `Uint8Array`, or something else). + * Value that is yielded as output. + * + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node that that yields. + * * If the plugin sets a {@link Parser `Parser`}, this should be the + * node that it yields. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be + * result it yields. * @this - * The current processor. - * Plugins can configure the processor by interacting with `this.Parser` or - * `this.Compiler`, or by accessing the data associated with the whole process - * (`this.data`). - * @param settings - * Configuration for plugin. + * Processor the attacher is applied to. + * @param parameters + * Arguments passed to the plugin. + * * Plugins typically receive one options object, but could receive other and * more values. - * Users can also pass a boolean instead of settings: `true` (to turn - * a plugin on) or `false` (to turn a plugin off). - * When a plugin is turned off, it won’t be called. - * - * When creating your own plugins, please accept only a single object! - * It allows plugins to be reconfigured and it helps users to know that every - * plugin accepts one options object. * @returns - * Plugins can return a `Transformer` to specify how the syntax tree is - * handled. + * Optional transform. */ export type Plugin< - PluginParameters extends any[] = any[], - Input = Node, + Parameters extends unknown[] = [], + Input extends Node | string | undefined = undefined, Output = Input > = ( - this: Input extends Node - ? Output extends Node - ? // This is a transform, so define `Input` as the current tree. - Processor - : // Compiler. - Processor - : Output extends Node - ? // Parser. - Processor - : // No clue. - Processor, - ...settings: PluginParameters -) => // If both `Input` and `Output` are `Node`, expect an optional `Transformer`. -Input extends Node - ? Output extends Node - ? Transformer | void - : void - : void + this: Processor, + ...parameters: Parameters +) => Input extends string + ? // Parser. + Output extends Node | undefined + ? undefined | void + : never + : Output extends CompileResults + ? // Compiler + Input extends Node | undefined + ? undefined | void + : never + : + | Transformer< + Input extends Node ? Input : Node, + Output extends Node ? Output : Node + > + | undefined + | void /** - * Presets provide a sharable way to configure processors with multiple plugins - * and/or settings. + * Presets are sharable configuration. + * + * They can contain plugins and settings. */ export type Preset = { + /** + * List of plugins and presets. + */ plugins?: PluggableList + + /** + * Shared settings for parsers and compilers. + */ settings?: Record } /** - * A tuple of a plugin and its setting(s). - * The first item is a plugin (function) to use and other items are options. - * Plugins are deduped based on identity: passing a function in twice will - * cause it to run only once. + * Tuple of a plugin and its setting(s). + * The first item is a plugin, the rest are its parameters. * - * @typeParam PluginParameters - * Plugin settings. + * @typeParam Parameters + * Arguments passed to the plugin. * @typeParam Input - * Value that is accepted by the plugin. + * Value that is expected as input. * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer expects. - * * If the plugin sets a parser, then this should be `string`. - * * If the plugin sets a compiler, then this should be the node type that - * the compiler expects. + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node it expects. + * * If the plugin sets a {@link Parser `Parser`}, this should be + * `string`. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be the + * node it expects. * @typeParam Output - * Value that the plugin yields. - * - * * If the plugin returns a transformer, then this should be the node - * type that the transformer yields, and defaults to `Input`. - * * If the plugin sets a parser, then this should be the node type that - * the parser yields. - * * If the plugin sets a compiler, then this should be the result that - * the compiler yields (`string`, `Uint8Array`, or something else). + * Value that is yielded as output. + * + * * If the plugin returns a {@link Transformer `Transformer`}, this + * should be the node that that yields. + * * If the plugin sets a {@link Parser `Parser`}, this should be the + * node that it yields. + * * If the plugin sets a {@link Compiler `Compiler`}, this should be + * result it yields. */ export type PluginTuple< - PluginParameters extends any[] = any[], - Input = Node, - Output = Input -> = [Plugin, ...PluginParameters] + Parameters extends unknown[] = [], + Input extends Node | string | undefined = undefined, + Output = undefined +> = [Plugin, ...Parameters] /** * A union of the different ways to add plugins and settings. - * - * @typeParam PluginParameters - * Plugin settings. */ -export type Pluggable = - | PluginTuple - | Plugin +export type Pluggable = + | Plugin + | PluginTuple | Preset /** @@ -604,94 +889,103 @@ export type Pluggable = */ export type PluggableList = Pluggable[] +// To do: remove? /** + * Attacher. + * * @deprecated * Please use `Plugin`. */ export type Attacher< - PluginParameters extends any[] = any[], - Input = Node, - Output = Input -> = Plugin + Parameters extends unknown[] = unknown[], + Input extends Node | string = Node, + Output extends CompileResults | Node = Input +> = Plugin /** - * Transformers modify the syntax tree or metadata of a file. - * A transformer is a function that is called each time a file is passed - * through the transform phase. - * If an error occurs (either because it’s thrown, returned, rejected, or passed - * to `next`), the process stops. + * Transformers handle syntax trees and files. + * + * They are functions that are called each time a syntax tree and file are + * passed through the run phase. + * When an error occurs in them (either because it’s thrown, returned, + * rejected, or passed to `next`), the process stops. + * + * The run phase is handled by [`trough`][trough], see its documentation for + * the exact semantics of these functions. + * + * [trough]: https://github.com/wooorm/trough#function-fninput-next * * @typeParam Input * Node type that the transformer expects. * @typeParam Output * Node type that the transformer yields. - * @param node - * Tree to be transformed. + * @param tree + * Tree to handle. * @param file - * File associated with node. + * File to handle. * @param next - * Callback that you must call when done. - * Note: this is given if you accept three parameters in your transformer. - * If you accept up to two parameters, it’s not given, and you can return - * a promise. + * Callback. * @returns - * Any of the following: - * - * * `void` — If nothing is returned, the next transformer keeps using same - * tree. - * * `Error` — Can be returned to stop the process. - * * `Node` — Can be returned and results in further transformations and - * `stringify`s to be performed on the new tree. - * * `Promise` — If a promise is returned, the function is asynchronous, and - * must be resolved (optionally with a `Node`) or rejected (optionally with - * an `Error`). - * - * If you accept a `next` callback, nothing should be returned. + * If you accept `next`, nothing. + * Otherwise: + * + * * `Error` — fatal error to stop the process + * * `Promise` or `undefined` — the next transformer keeps using + * same tree + * * `Promise` or `Node` — new, changed, tree */ export type Transformer< Input extends Node = Node, Output extends Node = Input > = ( - node: Input, + tree: Input, file: VFile, next: TransformCallback -) => Promise | Error | Output | undefined | void +) => + | Promise + | Promise // For some reason this is needed separately. + | Output + | Error + | undefined + | void /** - * Callback you must call when a transformer is done. + * If the signature of a `transformer` accepts a third argument, the + * transformer may perform asynchronous operations, and must call `next()`. * * @typeParam Tree - * Node that the plugin yields. + * Node type that the transformer yields. * @param error - * Pass an error to stop the process. - * @param node - * Pass a tree to continue transformations (and `stringify`) on the new tree. + * Fatal error to stop the process (optional). + * @param tree + * New, changed, tree (optional). * @param file - * Pass a file to continue transformations (and `stringify`) on the new file. + * New, changed, file (optional). * @returns * Nothing. */ -export type TransformCallback = ( +export type TransformCallback = ( error?: Error | undefined, - node?: Tree | undefined, + tree?: Output, file?: VFile | undefined -) => void +) => undefined /** - * Function handling the parsing of text to a syntax tree. - * Used in the parse phase in the process and called with a `string` and - * `VFile` representation of the document to parse. + * A **parser** handles the parsing of text to a syntax tree. * - * `Parser` can be a normal function, in which case it must return a `Node`: - * the syntax tree representation of the given file. + * It is used in the parse phase and is called with a `string` and + * {@link VFile `VFile`} of the document to parse. * - * `Parser` can also be a constructor function (a function with keys in its - * `prototype`), in which case it’s called with `new`. - * Instances must have a parse method that is called without arguments and - * must return a `Node`. + * `Parser` can be a normal function, in which case it must return the syntax + * tree representation of the given file ({@link Node `Node`}). + * + * `Parser` can also be a constructor function (a function with a `parse` + * field in its `prototype`), in which case it is constructed with `new`. + * Instances must have a `parse` method that is called without arguments and must + * return a {@link Node `Node`}. * * @typeParam Tree - * The node that the parser yields (and `run` receives). + * The node that the parser yields. */ export type Parser = | ParserClass @@ -728,7 +1022,7 @@ export class ParserClass { } /** - * Normal function to parse a file. + * Regular function to parse a file. * * @typeParam Tree * The node that the parser yields. @@ -745,17 +1039,32 @@ export type ParserFunction = ( ) => Tree /** - * Function handling the compilation of syntax tree to a text. - * Used in the stringify phase in the process and called with a `Node` and - * `VFile` representation of the document to stringify. + * A **compiler** handles the compiling of a syntax tree to something else (in + * most cases, text). + * + * It is used in the stringify phase and called with a {@link Node `Node`} + * and {@link VFile `VFile`} representation of the document to compile. + * + * `Compiler` can be a normal function, in which case it should return the + * textual representation of the given tree (`string`). + * + * `Compiler` can also be a constructor function (a function with a `compile` + * field in its `prototype`), in which case it is constructed with `new`. + * Instances must have a `compile` method that is called without arguments and + * should return a `string`. * - * `Compiler` can be a normal function, in which case it must return a - * `string`: the text representation of the given syntax tree. + * > 👉 **Note**: unified typically compiles by serializing: most compilers + * > return `string` (or `Uint8Array`). + * > Some compilers, such as the one configured with + * > [`rehype-react`][rehype-react], return other values (in this case, a + * > React tree). + * > If you’re using a compiler that doesn’t serialize, expect different result + * > values. + * > + * > To register custom results in TypeScript, add them to + * > {@link CompileResultMap `CompileResultMap`}. * - * `Compiler` can also be a constructor function (a function with keys in its - * `prototype`), in which case it’s called with `new`. - * Instances must have a `compile` method that is called without arguments - * and must return a `string`. + * [rehype-react]: https://github.com/rehypejs/rehype-react * * @typeParam Tree * The node that the compiler receives. @@ -800,7 +1109,7 @@ export class CompilerClass { } /** - * Normal function to compile a tree. + * Regular function to compile a tree. * * @typeParam Tree * The node that the compiler receives. @@ -814,47 +1123,52 @@ export class CompilerClass { * New content: compiled text (`string` or `Uint8Array`, for `file.value`) or * something else (for `file.result`). */ -export type CompilerFunction = ( - tree: Tree, - file: VFile -) => Result +export type CompilerFunction< + Tree extends Node = Node, + Result = CompileResults +> = (tree: Tree, file: VFile) => Result /** - * Callback called when a done running. + * Callback called when transformers are done. + * + * Called with either an error or results. + * * @typeParam Tree * The tree that the callback receives. * @param error - * Error passed when unsuccessful. - * @param node - * Tree to transform. + * Fatal error. + * @param tree + * Transformed tree. * @param file - * File passed when successful. + * File. * @returns * Nothing. */ export type RunCallback = ( error?: Error | undefined, - node?: Tree | undefined, + tree?: Tree | undefined, file?: VFile | undefined -) => void +) => undefined /** - * Callback called when a done processing. + * Callback called when the process is done. + * + * Called with either an error or a result. * * @typeParam File * The file that the callback receives. * @param error - * Error passed when unsuccessful. + * Fatal error. * @param file - * File passed when successful. + * Processed file. * @returns * Nothing. */ export type ProcessCallback = ( error?: Error | undefined, file?: File | undefined -) => void +) => undefined /** * A frozen processor. diff --git a/index.test-d.ts b/index.test-d.ts index b51f9fb1..bae224c9 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -39,26 +39,17 @@ const mdastRoot: MdastRoot = { // # Explicitly typed plugins // ## Plugin w/o options -const pluginWithoutOptions: Plugin<[]> = function () { +const pluginWithoutOptions: Plugin = function () { // Empty. } unified().use(pluginWithoutOptions) -unified().use( - pluginWithoutOptions, - // @ts-expect-error: plugin does not expect options. - {} -) -unified().use( - pluginWithoutOptions, - // @ts-expect-error: plugin does not expect `string` as options. - '' -) -unified().use( - pluginWithoutOptions, - // @ts-expect-error: plugin does not expect anything. - undefined -) +// @ts-expect-error: plugin does not expect options. +unified().use(pluginWithoutOptions, {}) +// @ts-expect-error: plugin does not expect `string` as options. +unified().use(pluginWithoutOptions, '') +// @ts-expect-error: plugin does not expect anything. +unified().use(pluginWithoutOptions, undefined) // ## Plugin w/ optional options const pluginWithOptionalOptions: Plugin< @@ -72,11 +63,8 @@ unified().use(pluginWithOptionalOptions, {}) unified().use(pluginWithOptionalOptions, {example: null}) unified().use(pluginWithOptionalOptions, {example: undefined}) unified().use(pluginWithOptionalOptions, {example: 'asd'}) -unified().use( - pluginWithOptionalOptions, - // @ts-expect-error: plugin does not accept `whatever`. - {whatever: 1} -) +// @ts-expect-error: plugin does not accept `whatever`. +unified().use(pluginWithOptionalOptions, {whatever: 1}) // ## Plugin w/ required options const pluginWithOptions: Plugin<[ExampleRequiredOptions]> = function (options) { @@ -85,11 +73,8 @@ const pluginWithOptions: Plugin<[ExampleRequiredOptions]> = function (options) { // @ts-expect-error: plugin requires options. unified().use(pluginWithOptions) -unified().use( - pluginWithOptions, - // @ts-expect-error: plugin requires particular option. - {} -) +// @ts-expect-error: plugin requires particular option. +unified().use(pluginWithOptions, {}) unified().use(pluginWithOptions, {example: ''}) // ## Plugin w/ several arguments @@ -101,16 +86,10 @@ const pluginWithSeveralArguments: Plugin<[ExampleRequiredOptions, number]> = // @ts-expect-error: plugin requires options. unified().use(pluginWithSeveralArguments) -unified().use( - pluginWithSeveralArguments, - // @ts-expect-error: plugin requires particular option. - {} -) -unified().use( - pluginWithSeveralArguments, - // @ts-expect-error: plugin requires more arguments. - {example: ''} -) +// @ts-expect-error: plugin requires particular option. +unified().use(pluginWithSeveralArguments, {}) +// @ts-expect-error: plugin requires more arguments. +unified().use(pluginWithSeveralArguments, {example: ''}) unified().use(pluginWithSeveralArguments, {example: ''}, 1) // # Implicitly typed plugins. @@ -265,7 +244,7 @@ unified() return undefined } }) - // Sync yielding implicit void. + // Sync yielding implicit `void` (because TS). .use(function () { return function () { // Empty. @@ -280,7 +259,7 @@ unified() // Sync throwing error. .use(function () { return function (x) { - // To do: investigate if we can support `never` by dropping this useless condition. + // Note: TS doesn’t like the `never` if we remove this useless condition. if (x) { throw new Error('x') } @@ -338,14 +317,13 @@ unified() return {type: 'x'} } }) - // To do: investigate why TS barfs on `Promise`? - // // Resolving explicit `undefined`. - // .use(function () { - // return async function () { - // return undefined - // } - // }) - // Resolving implicit void. + // Resolving explicit `undefined`. + .use(function () { + return async function () { + return undefined + } + }) + // Resolving implicit `void` (because TS). .use(function () { return async function () { // Empty. @@ -354,7 +332,6 @@ unified() // Rejecting error. .use(function () { return async function (x) { - // To do: investigate if we can support `never` by dropping this useless condition. if (x) { throw new Error('x') } @@ -368,23 +345,13 @@ const remarkParse: Plugin<[], string, MdastRoot> = function () { // Empty. } -const processorWithRemarkParse = unified() - .use(remarkParse) - .use(function () { - return function (tree) { - expectType(tree) - } - }) +const processorWithRemarkParse = unified().use(remarkParse) -expectType>(processorWithRemarkParse) +expectType>(processorWithRemarkParse) expectType(processorWithRemarkParse.parse('')) -// To do: accept `UnistNode`? -expectType(processorWithRemarkParse.runSync(mdastRoot)) -// @ts-expect-error: to do: accept `UnistNode`? -expectType(processorWithRemarkParse.runSync(hastRoot)) -// To do: yield `never`, accept `UnistNode`? -expectType(processorWithRemarkParse.stringify(mdastRoot)) -// @ts-expect-error: to do: accept `UnistNode`? +expectType(processorWithRemarkParse.runSync(mdastRoot)) +expectType(processorWithRemarkParse.runSync(hastRoot)) +expectType(processorWithRemarkParse.stringify(mdastRoot)) processorWithRemarkParse.stringify(hastRoot) expectType(processorWithRemarkParse.processSync('')) @@ -393,25 +360,15 @@ const remarkLint: Plugin<[], MdastRoot> = function () { // Empty. } -const processorWithRemarkLint = unified() - .use(remarkLint) - .use(function () { - return function (tree) { - expectType(tree) - } - }) +const processorWithRemarkLint = unified().use(remarkLint) -// To do: `UnistNode`, `MdastRoot`, `UnistNode`? -expectType>(processorWithRemarkLint) -// To do: yield `UnistNode`? -expectType(processorWithRemarkLint.parse('')) +expectType>(processorWithRemarkLint) +expectType(processorWithRemarkLint.parse('')) expectType(processorWithRemarkLint.runSync(mdastRoot)) // @ts-expect-error: not the correct node type. -expectType(processorWithRemarkLint.runSync(hastRoot)) -// To do: yield `never`, accept `UnistNode`? -expectType(processorWithRemarkLint.stringify(mdastRoot)) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRemarkLint.stringify(hastRoot) +processorWithRemarkLint.runSync(hastRoot) +expectType(processorWithRemarkLint.stringify(mdastRoot)) +expectType(processorWithRemarkLint.stringify(hastRoot)) expectType(processorWithRemarkLint.processSync('')) // Inspect/transform plugin (implicit). @@ -422,27 +379,21 @@ function remarkLintImplicit() { } } -const processorWithRemarkLintImplicit = unified() - .use(remarkLintImplicit) - .use(function () { - return function (tree) { - expectType(tree) - } - }) +const processorWithRemarkLintImplicit = unified().use(remarkLintImplicit) -// To do: `UnistNode`, `MdastRoot`, `UnistNode`? -expectType>( +expectType>( processorWithRemarkLintImplicit ) -// To do: yield `UnistNode`? -expectType(processorWithRemarkLintImplicit.parse('')) +expectType(processorWithRemarkLintImplicit.parse('')) expectType(processorWithRemarkLintImplicit.runSync(mdastRoot)) // @ts-expect-error: not the correct node type. processorWithRemarkLintImplicit.runSync(hastRoot) -// To do: yield `never`, accept `UnistNode`? -expectType(processorWithRemarkLintImplicit.stringify(mdastRoot)) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRemarkLintImplicit.stringify(hastRoot) +expectType( + processorWithRemarkLintImplicit.stringify(mdastRoot) +) +expectType( + processorWithRemarkLintImplicit.stringify(hastRoot) +) expectType(processorWithRemarkLintImplicit.processSync('')) // Mutate plugin (explicit). @@ -450,25 +401,15 @@ const remarkRehype: Plugin<[], MdastRoot, HastRoot> = function () { // Empty. } -const processorWithRemarkRehype = unified() - .use(remarkRehype) - .use(function () { - return function (tree) { - expectType(tree) - } - }) +const processorWithRemarkRehype = unified().use(remarkRehype) -// To do: `UnistNode`, `MdastRoot`, `UnistNode`? -expectType>(processorWithRemarkRehype) -// To do: yield `UnistNode`? -expectType(processorWithRemarkRehype.parse('')) +expectType>(processorWithRemarkRehype) +expectType(processorWithRemarkRehype.parse('')) expectType(processorWithRemarkRehype.runSync(mdastRoot)) // @ts-expect-error: not the correct node type. processorWithRemarkRehype.runSync(hastRoot) -// To do: yield `never`? -expectType(processorWithRemarkRehype.stringify(hastRoot)) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRemarkRehype.stringify(mdastRoot) +expectType(processorWithRemarkRehype.stringify(hastRoot)) +expectType(processorWithRemarkRehype.stringify(mdastRoot)) expectType(processorWithRemarkRehype.processSync('')) // Mutate plugin (implicit). @@ -479,27 +420,21 @@ function remarkRehypeImplicit() { } } -const processorWithRemarkRehypeImplicit = unified() - .use(remarkRehypeImplicit) - .use(function () { - return function (tree) { - expectType(tree) - } - }) +const processorWithRemarkRehypeImplicit = unified().use(remarkRehypeImplicit) -// To do: `UnistNode`, `MdastRoot`, `UnistNode`? -expectType>( +expectType>( processorWithRemarkRehypeImplicit ) -// To do: yield `UnistNode`? -expectType(processorWithRemarkRehypeImplicit.parse('')) +expectType(processorWithRemarkRehypeImplicit.parse('')) expectType(processorWithRemarkRehypeImplicit.runSync(mdastRoot)) // @ts-expect-error: not the correct node type. processorWithRemarkRehypeImplicit.runSync(hastRoot) -// To do: yield `never`? -expectType(processorWithRemarkRehypeImplicit.stringify(hastRoot)) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRemarkRehypeImplicit.stringify(mdastRoot) +expectType( + processorWithRemarkRehypeImplicit.stringify(hastRoot) +) +expectType( + processorWithRemarkRehypeImplicit.stringify(mdastRoot) +) expectType(processorWithRemarkRehypeImplicit.processSync('')) // Compile plugin. @@ -509,16 +444,12 @@ const rehypeStringify: Plugin<[], HastRoot, string> = function () { const processorWithRehypeStringify = unified().use(rehypeStringify) -// To do: ? -expectType>( +expectType>( processorWithRehypeStringify ) -// To do: yield `UnistNode`? -expectType(processorWithRehypeStringify.parse('')) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRehypeStringify.runSync(mdastRoot) -// To do: accept, yield `UnistNode`? -expectType(processorWithRehypeStringify.runSync(hastRoot)) +expectType(processorWithRehypeStringify.parse('')) +expectType(processorWithRehypeStringify.runSync(mdastRoot)) +expectType(processorWithRehypeStringify.runSync(hastRoot)) expectType(processorWithRehypeStringify.stringify(hastRoot)) // @ts-expect-error: not the correct node type. processorWithRehypeStringify.stringify(mdastRoot) @@ -534,16 +465,12 @@ const processorWithRehypeStringifyUint8Array = unified().use( rehypeStringifyUint8Array ) -// To do: ? -expectType>( +expectType>( processorWithRehypeStringifyUint8Array ) -// To do: yield `UnistNode`? -expectType(processorWithRehypeStringifyUint8Array.parse('')) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRehypeStringifyUint8Array.runSync(mdastRoot) -// To do: accept, yield `UnistNode`? -expectType(processorWithRehypeStringifyUint8Array.runSync(hastRoot)) +expectType(processorWithRehypeStringifyUint8Array.parse('')) +expectType(processorWithRehypeStringifyUint8Array.runSync(mdastRoot)) +expectType(processorWithRehypeStringifyUint8Array.runSync(hastRoot)) expectType( processorWithRehypeStringifyUint8Array.stringify(hastRoot) ) @@ -551,6 +478,15 @@ expectType( processorWithRehypeStringifyUint8Array.stringify(mdastRoot) expectType(processorWithRehypeStringifyUint8Array.processSync('')) +/** + * Register our custom compile result. + */ +declare module './index.js' { + interface CompileResultMap { + ReactNode: ReactNode + } +} + // Compile plugin (to a non-node). const rehypeReact: Plugin<[], HastRoot, ReactNode> = function () { // Empty. @@ -558,16 +494,12 @@ const rehypeReact: Plugin<[], HastRoot, ReactNode> = function () { const processorWithRehypeReact = unified().use(rehypeReact) -// To do: ? -expectType>( +expectType>( processorWithRehypeReact ) -// To do: yield `UnistNode`? -expectType(processorWithRehypeReact.parse('')) -// @ts-expect-error: to do: accept `UnistNode`? -processorWithRehypeReact.runSync(mdastRoot) -// To do: accept, yield `UnistNode`? -expectType(processorWithRehypeReact.runSync(hastRoot)) +expectType(processorWithRehypeReact.parse('')) +expectType(processorWithRehypeReact.runSync(mdastRoot)) +expectType(processorWithRehypeReact.runSync(hastRoot)) expectType(processorWithRehypeReact.stringify(hastRoot)) // @ts-expect-error: not the correct node type. processorWithRehypeReact.stringify(mdastRoot) @@ -583,7 +515,9 @@ const processorWithAll = unified() .use(remarkRehype) .use(rehypeStringify) -expectType>(processorWithAll) +expectType>( + processorWithAll +) expectType(processorWithAll.parse('')) expectType(processorWithAll.runSync(mdastRoot)) // @ts-expect-error: not the correct node type. @@ -593,16 +527,23 @@ expectType(processorWithAll.stringify(hastRoot)) processorWithAll.stringify(mdastRoot) expectType(processorWithAll.processSync('')) +// Doesn’t matter how you apply, compiler, transformers, parser is also fine. +expectType>( + unified() + .use(rehypeStringify) + .use(remarkLint) + .use(remarkLintImplicit) + .use(remarkRehype) + .use(remarkParse) +) + // # Different ways to use plugins -expectType>( - unified().use([remarkParse]) -) +expectType(unified().use([remarkParse])) -expectType>( +expectType( unified().use([ remarkParse, - // @ts-expect-error: to do: investigate. remarkLint, remarkLintImplicit, remarkRehype, @@ -610,16 +551,14 @@ expectType>( ]) ) -expectType>( - // @ts-expect-error: to do: investigate. +expectType( unified().use({ plugins: [remarkParse] }) ) -expectType>( +expectType( unified().use({ - // @ts-expect-error: to do: investigate. plugins: [ remarkParse, remarkLint, @@ -630,9 +569,8 @@ expectType>( }) ) -expectType>( +expectType( unified().use({ - // @ts-expect-error: to do: investigate. plugins: [ remarkParse, remarkLint, @@ -666,5 +604,7 @@ const rehypeClassNames: Plugin<[], HastRoot> = function () { // Empty. } -// To do: investigate. -unified().use(remarkLint).use(rehypeClassNames) +// We currently only *use* types, we don’t crash if they are nonsensical. +expectType>( + unified().use(remarkLint).use(rehypeClassNames) +) diff --git a/lib/index.js b/lib/index.js index 670d6fe0..daa54c6c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -8,12 +8,12 @@ * @typedef {import('../index.js').Parser} Parser * @typedef {import('../index.js').Pluggable} Pluggable * @typedef {import('../index.js').PluggableList} PluggableList + * @typedef {import('../index.js').PluginTuple} PluginTuple * @typedef {import('../index.js').Plugin} Plugin * @typedef {import('../index.js').Preset} Preset * @typedef {import('../index.js').ProcessCallback} ProcessCallback * @typedef {import('../index.js').Processor} Processor * @typedef {import('../index.js').RunCallback} RunCallback - * @typedef {import('../index.js').Transformer} Transformer */ import structuredClone from '@ungap/structured-clone' @@ -52,7 +52,6 @@ function base() { // Plugins. processor.attachers = attachers - // @ts-expect-error: overloads are handled. processor.use = use // API. @@ -75,7 +74,8 @@ function base() { let index = -1 while (++index < attachers.length) { - destination.use(...attachers[index]) + const attacher = attachers[index] + destination.use(...attacher) } destination.data(structuredClone(namespace)) @@ -129,7 +129,6 @@ function base() { options[0] = undefined } - /** @type {Transformer | void} */ const transformer = attacher.call(processor, ...options) if (typeof transformer === 'function') { @@ -144,7 +143,7 @@ function base() { } /** - * @param {Pluggable | null | undefined} [value] + * @param {Exclude | PluggableList | null | undefined} [value] * @param {...unknown} options * @returns {Processor} */ @@ -176,15 +175,16 @@ function base() { return processor /** - * @param {import('../index.js').Pluggable>} value - * @returns {void} + * @param {import('../index.js').Pluggable} value + * @returns {undefined} */ function add(value) { if (typeof value === 'function') { addPlugin(value) } else if (typeof value === 'object') { if (Array.isArray(value)) { - const [plugin, ...options] = value + const [plugin, ...options] = + /** @type {[Plugin, ...Array]} */ (value) addPlugin(plugin, ...options) } else { addPreset(value) @@ -196,7 +196,7 @@ function base() { /** * @param {Preset} result - * @returns {void} + * @returns {undefined} */ function addPreset(result) { if (!('plugins' in result) && !('settings' in result)) { @@ -215,7 +215,7 @@ function base() { /** * @param {PluggableList | null | undefined} [plugins] - * @returns {void} + * @returns {undefined} */ function addList(plugins) { let index = -1 @@ -235,7 +235,7 @@ function base() { /** * @param {Plugin} plugin * @param {...unknown} [value] - * @returns {void} + * @returns {undefined} */ function addPlugin(plugin, value) { let index = -1 @@ -299,7 +299,7 @@ function base() { * @param {Node} node * @param {RunCallback | VFileCompatible} [doc] * @param {RunCallback} [callback] - * @returns {Promise | void} + * @returns {Promise | undefined} */ function run(node, doc, callback) { assertNode(node) @@ -316,10 +316,11 @@ function base() { executor(undefined, callback) + // Note: `void`s needed for TS. /** - * @param {((node: Node) => void) | undefined} resolve - * @param {(error: Error) => void} reject - * @returns {void} + * @param {((node: Node) => undefined | void) | undefined} resolve + * @param {(error: Error) => undefined | void} reject + * @returns {undefined} */ function executor(resolve, reject) { // @ts-expect-error: `doc` can’t be a callback anymore, we checked. @@ -329,7 +330,7 @@ function base() { * @param {Error | undefined} error * @param {Node} tree * @param {VFile} file - * @returns {void} + * @returns {undefined} */ function done(error, tree, file) { tree = tree || node @@ -362,7 +363,7 @@ function base() { /** * @param {Error | undefined} [error] * @param {Node} [tree] - * @returns {void} + * @returns {undefined} */ function done(error, tree) { bail(error) @@ -387,10 +388,11 @@ function base() { executor(undefined, callback) + // Note: `void`s needed for TS. /** - * @param {((file: VFile) => void) | undefined} resolve - * @param {(error?: Error | undefined) => void} reject - * @returns {void} + * @param {((file: VFile) => undefined | void) | undefined} resolve + * @param {(error?: Error | undefined) => undefined | void} reject + * @returns {undefined} */ function executor(resolve, reject) { const file = vfile(doc) @@ -417,7 +419,7 @@ function base() { /** * @param {Error | undefined} [error] * @param {VFile | undefined} [file] - * @returns {void} + * @returns {undefined} */ function done(error, file) { if (error || !file) { @@ -432,7 +434,7 @@ function base() { } } - /** @type {Processor['processSync']} */ + /** @type {import('../index.js').Processor['processSync']} */ function processSync(doc) { /** @type {boolean | undefined} */ let complete @@ -451,7 +453,7 @@ function base() { /** * @param {Error | undefined} [error] - * @returns {void} + * @returns {undefined} */ function done(error) { complete = true diff --git a/package.json b/package.json index b448ae3d..63df3d6e 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "xo": "^0.55.0" }, "scripts": { - "build": "tsc --build --clean && tsc --build && type-coverage", + "build": "tsc --build --clean && tsc --build && type-coverage && tsd", "format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix", "prepack": "npm run build && npm run format", "test": "npm run build && npm run format && npm run test-coverage", @@ -95,10 +95,6 @@ "atLeast": 100, "detail": true, "ignoreCatch": true, - "#": "`type-coverage` currently barfs on inferring nodes in plugins, while TS gets it", - "ignoreFiles": [ - "test/**/*.js" - ], "strict": true }, "xo": { @@ -109,6 +105,7 @@ ], "rules": { "@typescript-eslint/ban-types": "off", + "@typescript-eslint/consistent-type-definitions": "off", "@typescript-eslint/naming-convention": "off" } } diff --git a/readme.md b/readme.md index 003a4ad4..6ac7a007 100644 --- a/readme.md +++ b/readme.md @@ -460,7 +460,8 @@ Parse text to a syntax tree. ###### Parameters -* `file` ([`VFile`][vfile]) — any value accepted as `x` in `new VFile(x)` +* `file` ([`VFile`][vfile]) — file to parse; typically `string`; any value + accepted as `x` in `new VFile(x)` ###### Returns @@ -497,14 +498,15 @@ Yields: #### `processor.Parser` A **parser** handles the parsing of text to a syntax tree. + It is used in the [parse phase][overview] and is called with a `string` and [`VFile`][vfile] of the document to parse. `Parser` can be a normal function, in which case it must return the syntax tree representation of the given file ([`Node`][node]). -`Parser` can also be a constructor function (a function with a `parse` field, or -other fields, in its `prototype`), in which case it is constructed with `new`. +`Parser` can also be a constructor function (a function with a `parse` field in +its `prototype`), in which case it is constructed with `new`. Instances must have a `parse` method that is called without arguments and must return a [`Node`][node]. @@ -521,8 +523,8 @@ Compile a syntax tree. ###### Parameters * `tree` ([`Node`][node]) — tree to compile -* `file` ([`VFile`][vfile], optional) — any value accepted as `x` in - `new VFile(x)` +* `file` ([`VFile`][vfile], optional) — file associated with `node`; any + value accepted as `x` in `new VFile(x)` ###### Returns @@ -562,6 +564,7 @@ Yields: A **compiler** handles the compiling of a syntax tree to something else (in most cases, text). + It is used in the [stringify phase][overview] and called with a [`Node`][node] and [`VFile`][file] representation of the document to compile. @@ -569,7 +572,7 @@ and [`VFile`][file] representation of the document to compile. representation of the given tree (`string`). `Compiler` can also be a constructor function (a function with a `compile` -field, or other fields, in its `prototype`), in which case it is constructed +field in its `prototype`), in which case it is constructed with `new`. Instances must have a `compile` method that is called without arguments and should return a `string`. @@ -599,7 +602,7 @@ Run *[transformers][transformer]* on a syntax tree. ###### Returns -Nothing if `done` is given (`void`). +Nothing if `done` is given (`undefined`). A [`Promise`][promise] otherwise. The promise is rejected with a fatal error or resolved with the transformed tree ([`Node`][node]). @@ -639,6 +642,7 @@ Yields: #### `function done(err[, tree, file])` Callback called when transformers are done. + Called with either an error or results. ###### Parameters @@ -679,12 +683,13 @@ Process the given file as configured on the processor. ###### Parameters -* `file` ([`VFile`][vfile]) — any value accepted as `x` in `new VFile(x)` +* `file` ([`VFile`][vfile]) — file; any value accepted as `x` in + `new VFile(x)` * `done` ([`Function`][process-done], optional) — callback ###### Returns -Nothing if `done` is given (`void`). +Nothing if `done` is given (`undefined`). A [`Promise`][promise] otherwise. The promise is rejected with a fatal error or resolved with the processed file ([`VFile`][vfile]). @@ -742,6 +747,7 @@ Yields: #### `function done(err, file)` Callback called when the process is done. + Called with either an error or a result. ###### Parameters @@ -905,6 +911,7 @@ processor.data() // => {charlie: 'delta'} ### `processor.freeze()` Freeze a processor. + Frozen processors are meant to be extended and not to be configured directly. When a processor is frozen it cannot be unfrozen. @@ -1073,6 +1080,7 @@ Optional transform ([`Transformer`][transformer]). ### `function transformer(tree, file[, next])` Transformers handle syntax trees and files. + They are functions that are called each time a syntax tree and file are passed through the [run phase][overview]. When an error occurs in them (either because it’s thrown, returned, rejected, @@ -1084,16 +1092,18 @@ exact semantics of these functions. ###### Parameters * `tree` ([`Node`][node]) — tree to handle -* `file` ([`VFile`][vfile]) —file to handle -* `next` ([`Function`][next], optional) +* `file` ([`VFile`][vfile]) — file to handle +* `next` ([`Function`][next], optional) — callback ###### Returns -* `void` — the next transformer keeps using same tree +If you accept `next`, nothing. +Otherwise: + * `Error` — fatal error to stop the process -* [`Node`][node] — new, changed, tree -* `Promise` — resolved with a new, changed, tree or rejected with an - `Error` +* `Promise` or `undefined` — the next transformer keeps using same + tree +* `Promise` or [`Node`][node] — new, changed, tree #### `function next(err[, tree[, file]])` @@ -1109,6 +1119,7 @@ may perform asynchronous operations, and must call `next()`. ## `Preset` Presets are sharable configuration. + They can contain plugins and settings. ###### Example diff --git a/test/freeze.js b/test/freeze.js index fccbbc7c..a4097011 100644 --- a/test/freeze.js +++ b/test/freeze.js @@ -1,15 +1,11 @@ import assert from 'node:assert/strict' import test from 'node:test' import {unified} from 'unified' -import {SimpleCompiler, SimpleParser} from './util/simple.js' +import {simpleCompiler, simpleParser} from './util/simple.js' test('`freeze`', async function (t) { - const frozen = unified() - .use(function () { - this.Parser = SimpleParser - this.Compiler = SimpleCompiler - }) - .freeze() + const frozen = unified().use(parse).use(compile).freeze() + const unfrozen = frozen() await t.test('data', async function (t) { @@ -189,8 +185,20 @@ test('`freeze`', async function (t) { .use(function () { index++ }) - .use({plugins: [freezingPlugin]}) - .use({plugins: [freezingPlugin]}) + .use({ + plugins: [ + function () { + this.freeze() + } + ] + }) + .use({ + plugins: [ + function () { + this.freeze() + } + ] + }) .freeze() // To show it doesn’t do anything. .freeze() @@ -203,14 +211,24 @@ test('`freeze`', async function (t) { .freeze() assert.equal(index, 2) - - /** - * @satisfies {import('unified').Plugin<[]>} - * @this {import('unified').Processor} - */ - function freezingPlugin() { - this.freeze() - } }) }) }) + +// `this` in JS is buggy in TS. +/** + * @type {import('unified').Plugin<[], string, import('unist').Node>} + */ +function parse() { + // type-coverage:ignore-next-line -- something with TS being wrong. + this.Parser = simpleParser +} + +// `this` in JS is buggy in TS. +/** + * @type {import('unified').Plugin<[], import('unist').Node, string>} + */ +function compile() { + // type-coverage:ignore-next-line -- something with TS being wrong. + this.Compiler = simpleCompiler +} diff --git a/test/process-compilers.js b/test/process-compilers.js index c71a4093..dad6c4c1 100644 --- a/test/process-compilers.js +++ b/test/process-compilers.js @@ -1,14 +1,14 @@ import assert from 'node:assert/strict' import test from 'node:test' import {unified} from 'unified' -import {SimpleParser} from './util/simple.js' +import {simpleParser} from './util/simple.js' test('process (compilers)', async function (t) { await t.test('should compile `string`', async function () { const processor = unified() const result = 'bravo' - processor.Parser = SimpleParser + processor.Parser = simpleParser processor.Compiler = function () { return result } @@ -23,7 +23,7 @@ test('process (compilers)', async function (t) { const processor = unified() const result = new Uint8Array([0xef, 0xbb, 0xbf, 0x61, 0x62, 0x63]) - processor.Parser = SimpleParser + processor.Parser = simpleParser processor.Compiler = function () { return result } @@ -37,14 +37,13 @@ test('process (compilers)', async function (t) { await t.test('should compile `null`', async function () { const processor = unified() - processor.Parser = SimpleParser + processor.Parser = simpleParser processor.Compiler = function () { return null } const file = await processor.process('alpha') - // To do: is this right? assert.equal(file.value, 'alpha') assert.equal(file.result, undefined) }) @@ -59,7 +58,7 @@ test('process (compilers)', async function (t) { props: {children: ['bravo']} } - processor.Parser = SimpleParser + processor.Parser = simpleParser processor.Compiler = function () { return result } diff --git a/test/process-sync.js b/test/process-sync.js index a315a972..4184298d 100644 --- a/test/process-sync.js +++ b/test/process-sync.js @@ -1,7 +1,7 @@ import assert from 'node:assert/strict' import test from 'node:test' import {unified} from 'unified' -import {SimpleCompiler, SimpleParser} from './util/simple.js' +import {simpleCompiler, simpleParser} from './util/simple.js' test('`processSync`', async function (t) { await t.test('should throw w/o `Parser`', async function () { @@ -13,7 +13,7 @@ test('`processSync`', async function (t) { await t.test('should throw w/o `Compiler`', async function () { assert.throws(function () { const processor = unified() - processor.Parser = SimpleParser + processor.Parser = simpleParser processor.processSync('') }, /Cannot `processSync` without `Compiler`/) }) @@ -21,8 +21,8 @@ test('`processSync`', async function (t) { await t.test('should support `processSync`', async function () { const processor = unified() - processor.Parser = SimpleParser - processor.Compiler = SimpleCompiler + processor.Parser = simpleParser + processor.Compiler = simpleCompiler assert.equal(processor.processSync('alpha').toString(), 'alpha') }) @@ -31,11 +31,24 @@ test('`processSync`', async function (t) { 'should throw transform errors from `processSync`', async function () { assert.throws(function () { - unified() + const processor = unified() + processor.Parser = simpleParser + processor.Compiler = simpleCompiler + + processor .use(function () { - this.Parser = SimpleParser - this.Compiler = SimpleCompiler + return function () { + return new Error('bravo') + } + }) + .processSync('delta') + }, /Error: bravo/) + assert.throws(function () { + unified() + .use(parse) + .use(compile) + .use(function () { return function () { return new Error('bravo') } @@ -45,3 +58,21 @@ test('`processSync`', async function (t) { } ) }) + +// `this` in JS is buggy in TS. +/** + * @type {import('unified').Plugin<[], string, import('unist').Node>} + */ +function parse() { + // type-coverage:ignore-next-line -- something with TS being wrong. + this.Parser = simpleParser +} + +// `this` in JS is buggy in TS. +/** + * @type {import('unified').Plugin<[], import('unist').Node, string>} + */ +function compile() { + // type-coverage:ignore-next-line -- something with TS being wrong. + this.Compiler = simpleCompiler +} diff --git a/test/process.js b/test/process.js index b43360c3..e2290fb6 100644 --- a/test/process.js +++ b/test/process.js @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import test from 'node:test' import {unified} from 'unified' import {VFile} from 'vfile' -import {SimpleCompiler, SimpleParser} from './util/simple.js' +import {simpleCompiler, simpleParser} from './util/simple.js' test('`process`', async function (t) { const givenFile = new VFile('alpha') @@ -17,7 +17,7 @@ test('`process`', async function (t) { await t.test('should throw w/o `Compiler`', async function () { assert.throws(function () { const processor = unified() - processor.Parser = SimpleParser + processor.Parser = simpleParser processor.process('') }, /Cannot `process` without `Compiler`/) }) @@ -59,8 +59,8 @@ test('`process`', async function (t) { await t.test('should rethrow errors in `done` throws', async function () { const processor = unified() - processor.Parser = SimpleParser - processor.Compiler = SimpleCompiler + processor.Parser = simpleParser + processor.Compiler = simpleCompiler assert.throws(function () { processor.process(givenFile, function () { @@ -74,8 +74,8 @@ test('`process`', async function (t) { async function () { const processor = unified() - processor.Parser = SimpleParser - processor.Compiler = SimpleCompiler + processor.Parser = simpleParser + processor.Compiler = simpleCompiler await new Promise(function (resolve, reject) { processor.process(givenFile).then( diff --git a/test/run-sync.js b/test/run-sync.js index 6af4bb32..8625f2ce 100644 --- a/test/run-sync.js +++ b/test/run-sync.js @@ -152,17 +152,11 @@ test('`runSync`', async function (t) { assert.throws(function () { unified() - .use( - // Note: TS doesn’t understand `Promise`. - /** - * @type {import('unified').Plugin<[]>} - */ - function () { - return async function () { - throw givenError - } + .use(function () { + return async function () { + throw givenError } - ) + }) .runSync(givenNode) }, /`runSync` finished async. Use `run` instead/) }) diff --git a/test/run.js b/test/run.js index 9a8fb1c0..3032fa5b 100644 --- a/test/run.js +++ b/test/run.js @@ -256,18 +256,11 @@ test('`run`', async function (t) { return async function () {} }) // Async transformer w/ explicit `undefined`. - .use( - // Note: TS doesn’t understand w/o explicit `this` type. - /** - * @satisfies {import('unified').Plugin<[]>} - * @this {import('unified').Processor} - */ - function () { - return async function () { - return undefined - } + .use(function () { + return async function () { + return undefined } - ) + }) .use(function () { return async function (tree, file) { assert.equal(tree, givenNode) @@ -342,6 +335,9 @@ test('`run`', async function (t) { function () { reject(new Error('should reject')) }, + /** + * @param {unknown} error + */ function (error) { assert.equal(error, givenError) resolve(undefined) @@ -385,6 +381,9 @@ test('`run`', async function (t) { function () { reject(new Error('should reject')) }, + /** + * @param {unknown} error + */ function (error) { assert.equal(error, givenError) resolve(undefined) @@ -411,6 +410,9 @@ test('`run`', async function (t) { function () { reject(new Error('should reject')) }, + /** + * @param {unknown} error + */ function (error) { assert.equal(error, givenError) resolve(undefined) @@ -477,6 +479,9 @@ test('`run`', async function (t) { function () { reject(new Error('should reject')) }, + /** + * @param {unknown} error + */ function (error) { assert.equal(error, givenError) resolve(undefined) @@ -557,18 +562,11 @@ test('`run`', async function (t) { return async function () {} }) // Async transformer w/ explicit `undefined`. - .use( - // Note: TS doesn’t understand w/o explicit `this` type. - /** - * @satisfies {import('unified').Plugin<[]>} - * @this {import('unified').Processor} - */ - function () { - return async function () { - return undefined - } + .use(function () { + return async function () { + return undefined } - ) + }) .use(function () { return async function (tree, file) { assert.equal(tree, givenNode) diff --git a/test/stringify.js b/test/stringify.js index 652c052a..ad86b19d 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -76,6 +76,7 @@ test('`stringify`', async function (t) { assert.equal(arguments.length, 2) } + // type-coverage:ignore-next-line -- for some reason TS does understand `Parser.prototype`, but not `Compiler.prototype`. processor.Compiler.prototype.compile = function () { assert.equal(arguments.length, 0) return 'echo' diff --git a/test/use.js b/test/use.js index 6ea35de9..4f0f4523 100644 --- a/test/use.js +++ b/test/use.js @@ -11,22 +11,16 @@ test('`use`', async function (t) { await t.test('should ignore no value', function () { const processor = unified() - // To do: investigate if we can enable it. - // @ts-expect-error: check how the runtime handles a missing value. assert.equal(processor.use(), processor) }) await t.test('should ignore `undefined`', function () { const processor = unified() - // To do: investigate if we can enable it. - // @ts-expect-error: check how the runtime handles `undefined`. assert.equal(processor.use(undefined), processor) }) await t.test('should ignore `null`', function () { const processor = unified() - // To do: investigate if we can enable it. - // @ts-expect-error: check how the runtime handles `null`. assert.equal(processor.use(null), processor) }) @@ -99,12 +93,6 @@ test('`use`', async function (t) { assert.equal(arguments.length, 0) calls++ }, - // Note: see if we can remove this line? If we remove the previous `arguments.length` assertion, - // TS infers the plugin fine. But with it, it thinks it’s a tuple? - /** - * @satisfies {import('unified').Plugin<[]>} - * @this {import('unified').Processor} - */ function () { assert.equal(this, processor) assert.equal(arguments.length, 0) @@ -211,10 +199,8 @@ test('`use`', async function (t) { 'should throw when given a preset w/ invalid `plugins` (`false`)', async function () { assert.throws(function () { - unified().use({ - // @ts-expect-error: check how invalid `plugins` is handled. - plugins: false - }) + // @ts-expect-error: check how invalid `plugins` is handled. + unified().use({plugins: false}) }, /Expected a list of plugins, not `false`/) } ) diff --git a/test/util/simple.js b/test/util/simple.js index 41abd306..f0b60f2f 100644 --- a/test/util/simple.js +++ b/test/util/simple.js @@ -1,35 +1,23 @@ /** - * @typedef {import('unified').Parser} Parser - * @typedef {import('unified').Compiler} Compiler + * @typedef {import('unist').Node} Node * @typedef {import('unist').Literal} Literal */ // Make references to the above types visible in VS Code. '' -/** @type {Parser} */ -export class SimpleParser { - /** @param {string} doc */ - constructor(doc) { - /** @type {string} */ - this.value = doc - } - - /** @returns {Literal} */ - parse() { - return {type: 'text', value: this.value} - } +/** + * @param {string} value + * @returns {Literal} + */ +export function simpleParser(value) { + return /** @type {Literal} */ ({type: 'text', value}) } -/** @type {Compiler} */ -export class SimpleCompiler { - /** @param {Literal} node */ - constructor(node) { - /** @type {Literal} */ - this.node = node - } - - compile() { - return this.node.value - } +/** + * @param {Node} node + * @returns {string} + */ +export function simpleCompiler(node) { + return 'value' in node && typeof node.value === 'string' ? node.value : '' }