/** * @typedef {import('trough').Pipeline} Pipeline * * @typedef {import('unist').Node} Node * * @typedef {import('vfile').Compatible} Compatible * @typedef {import('vfile').Value} Value * * @typedef {import('../index.js').CompileResultMap} CompileResultMap * @typedef {import('../index.js').Data} Data * @typedef {import('../index.js').Settings} Settings */ /** * @typedef {CompileResultMap[keyof CompileResultMap]} CompileResults * Acceptable results from compilers. * * To register custom results, add them to * {@link CompileResultMap `CompileResultMap`}. */ /** * @template {Node} [Tree=Node] * The node that the compiler receives (default: `Node`). * @template {CompileResults} [Result=CompileResults] * The thing that the compiler yields (default: `CompileResults`). * @callback Compiler * A **compiler** handles the compiling of a syntax tree to something else * (in most cases, text) (TypeScript type). * * It is used in the stringify phase and called with a {@link Node `Node`} * and {@link VFile `VFile`} representation of the document to compile. * It should return the textual representation of the given tree (typically * `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 * @param {Tree} tree * Tree to compile. * @param {VFile} file * File associated with `tree`. * @returns {Result} * New content: compiled text (`string` or `Uint8Array`, for `file.value`) or * something else (for `file.result`). */ /** * @template {Node} [Tree=Node] * The node that the parser yields (default: `Node`) * @callback Parser * 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. * It must return the syntax tree representation of the given file * ({@link Node `Node`}). * @param {string} document * Document to parse. * @param {VFile} file * File associated with `document`. * @returns {Tree} * Node representing the given file. */ /** * @typedef {( * Plugin, any, any> | * PluginTuple, any, any> | * Preset * )} Pluggable * Union of the different ways to add plugins and settings. */ /** * @typedef {Array} PluggableList * List of plugins and presets. */ // Note: we can’t use `callback` yet as it messes up `this`: // . /** * @template {Array} [PluginParameters=[]] * Arguments passed to the plugin (default: `[]`, the empty tuple). * @template {Node | string | undefined} [Input=Node] * Value that is expected as input (default: `Node`). * * * 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. * @template [Output=Input] * Value that is yielded as output (default: `Input`). * * * 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. * @typedef {( * (this: Processor, ...parameters: PluginParameters) => * 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 * )} Plugin * Single plugin. * * 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 * * In practice, they are functions that can receive options and configure the * processor (`this`). * * > 👉 **Note**: plugins are called when the processor is *frozen*, not when * > they are applied. */ /** * Tuple of a plugin and its configuration. * * The first item is a plugin, the rest are its parameters. * * @template {Array} [TupleParameters=[]] * Arguments passed to the plugin (default: `[]`, the empty tuple). * @template {Node | string | undefined} [Input=undefined] * Value that is expected as input (optional). * * * 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. * @template [Output=undefined] (optional). * 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. * @typedef {( * [ * plugin: Plugin, * ...parameters: TupleParameters * ] * )} PluginTuple */ /** * @typedef Preset * Sharable configuration. * * They can contain plugins and settings. * @property {PluggableList | undefined} [plugins] * List of plugins and presets (optional). * @property {Settings | undefined} [settings] * Shared settings for parsers and compilers (optional). */ /** * @template {VFile} [File=VFile] * The file that the callback receives (default: `VFile`). * @callback ProcessCallback * Callback called when the process is done. * * Called with either an error or a result. * @param {Error | undefined} [error] * Fatal error (optional). * @param {File | undefined} [file] * Processed file (optional). * @returns {undefined} * Nothing. */ /** * @template {Node} [Tree=Node] * The tree that the callback receives (default: `Node`). * @callback RunCallback * Callback called when transformers are done. * * Called with either an error or results. * @param {Error | undefined} [error] * Fatal error (optional). * @param {Tree | undefined} [tree] * Transformed tree (optional). * @param {VFile | undefined} [file] * File (optional). * @returns {undefined} * Nothing. */ /** * @template {Node} [Output=Node] * Node type that the transformer yields (default: `Node`). * @callback TransformCallback * Callback passed to transforms. * * If the signature of a `transformer` accepts a third argument, the * transformer may perform asynchronous operations, and must call it. * @param {Error | undefined} [error] * Fatal error to stop the process (optional). * @param {Output | undefined} [tree] * New, changed, tree (optional). * @param {VFile | undefined} [file] * New, changed, file (optional). * @returns {undefined} * Nothing. */ /** * @template {Node} [Input=Node] * Node type that the transformer expects (default: `Node`). * @template {Node} [Output=Input] * Node type that the transformer yields (default: `Input`). * @callback Transformer * 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. * * > 👉 **Note**: you should likely ignore `next`: don’t accept it. * > it supports callback-style async work. * > But promises are likely easier to reason about. * * [trough]: https://github.com/wooorm/trough#function-fninput-next * @param {Input} tree * Tree to handle. * @param {VFile} file * File to handle. * @param {TransformCallback} next * Callback. * @returns {( * Promise | * Promise | // For some reason this is needed separately. * Output | * Error | * undefined | * void * )} * 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 */ /** * @template {Node | undefined} ParseTree * Output of `parse`. * @template {Node | undefined} HeadTree * Input for `run`. * @template {Node | undefined} TailTree * Output for `run`. * @template {Node | undefined} CompileTree * Input of `stringify`. * @template {CompileResults | undefined} CompileResult * Output of `stringify`. * @template {Node | string | undefined} Input * Input of plugin. * @template Output * Output of plugin (optional). * @typedef {( * Input extends string * ? Output extends Node | undefined * ? // Parser. * Processor< * Output extends undefined ? ParseTree : Output, * HeadTree, * TailTree, * CompileTree, * CompileResult * > * : // 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< * ParseTree, * HeadTree extends undefined ? Input : HeadTree, * Output extends undefined ? TailTree : Output, * CompileTree, * CompileResult * > * : // Unknown. * Processor * : // Unknown. * Processor * )} UsePlugin * Create a processor based on the input/output of a {@link Plugin plugin}. */ /** * @template {CompileResults | undefined} Result * Node type that the transformer yields. * @typedef {( * Result extends Value | undefined ? * VFile : * VFile & {result: Result} * )} VFileWithOutput * 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`}. */ import {bail} from 'bail' import extend from 'extend' import {ok as assert} from 'devlop' import isPlainObj from 'is-plain-obj' import {trough} from 'trough' import {VFile} from 'vfile' import {CallableInstance} from './callable-instance.js' // To do: next major: drop `Compiler`, `Parser`: prefer lowercase. // To do: we could start yielding `never` in TS when a parser is missing and // `parse` is called. // Currently, we allow directly setting `processor.parser`, which is untyped. const own = {}.hasOwnProperty /** * @template {Node | undefined} [ParseTree=undefined] * Output of `parse` (optional). * @template {Node | undefined} [HeadTree=undefined] * Input for `run` (optional). * @template {Node | undefined} [TailTree=undefined] * Output for `run` (optional). * @template {Node | undefined} [CompileTree=undefined] * Input of `stringify` (optional). * @template {CompileResults | undefined} [CompileResult=undefined] * Output of `stringify` (optional). * @extends {CallableInstance<[], Processor>} */ export class Processor extends CallableInstance { /** * Create a processor. */ constructor() { // If `Processor()` is called (w/o new), `copy` is called instead. super('copy') /** * Compiler to use (deprecated). * * @deprecated * Use `compiler` instead. * @type {( * Compiler< * CompileTree extends undefined ? Node : CompileTree, * CompileResult extends undefined ? CompileResults : CompileResult * > | * undefined * )} */ this.Compiler = undefined /** * Parser to use (deprecated). * * @deprecated * Use `parser` instead. * @type {( * Parser | * undefined * )} */ this.Parser = undefined // Note: the following fields are considered private. // However, they are needed for tests, and TSC generates an untyped // `private freezeIndex` field for, which trips `type-coverage` up. // Instead, we use `@deprecated` to visualize that they shouldn’t be used. /** * Internal list of configured plugins. * * @deprecated * This is a private internal property and should not be used. * @type {Array>>} */ this.attachers = [] /** * Compiler to use. * * @type {( * Compiler< * CompileTree extends undefined ? Node : CompileTree, * CompileResult extends undefined ? CompileResults : CompileResult * > | * undefined * )} */ this.compiler = undefined /** * Internal state to track where we are while freezing. * * @deprecated * This is a private internal property and should not be used. * @type {number} */ this.freezeIndex = -1 /** * Internal state to track whether we’re frozen. * * @deprecated * This is a private internal property and should not be used. * @type {boolean | undefined} */ this.frozen = undefined /** * Internal state. * * @deprecated * This is a private internal property and should not be used. * @type {Data} */ this.namespace = {} /** * Parser to use. * * @type {( * Parser | * undefined * )} */ this.parser = undefined /** * Internal list of configured transformers. * * @deprecated * This is a private internal property and should not be used. * @type {Pipeline} */ this.transformers = trough() } /** * Copy a processor. * * @deprecated * This is a private internal method and should not be used. * @returns {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. */ copy() { // Cast as the type parameters will be the same after attaching. const destination = /** @type {Processor} */ ( new Processor() ) let index = -1 while (++index < this.attachers.length) { const attacher = this.attachers[index] destination.use(...attacher) } destination.data(extend(true, {}, this.namespace)) return destination } /** * 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. * * > 👉 **Note**: to register custom data in TypeScript, augment the * > {@link Data `Data`} interface. * * @example * This example show how to get and set info: * * ```js * import {unified} from 'unified' * * const processor = unified().data('alpha', 'bravo') * * processor.data('alpha') // => 'bravo' * * processor.data() // => {alpha: 'bravo'} * * processor.data({charlie: 'delta'}) * * processor.data() // => {charlie: 'delta'} * ``` * * @template {keyof Data} Key * * @overload * @returns {Data} * * @overload * @param {Data} dataset * @returns {Processor} * * @overload * @param {Key} key * @returns {Data[Key]} * * @overload * @param {Key} key * @param {Data[Key]} value * @returns {Processor} * * @param {Data | Key} [key] * Key to get or set, or entire dataset to set, or nothing to get the * entire dataset (optional). * @param {Data[Key]} [value] * Value to set (optional). * @returns {unknown} * The current processor when setting, the value at `key` when getting, or * the entire dataset when getting without key. */ data(key, value) { if (typeof key === 'string') { // Set `key`. if (arguments.length === 2) { assertUnfrozen('data', this.frozen) this.namespace[key] = value return this } // Get `key`. return (own.call(this.namespace, key) && this.namespace[key]) || undefined } // Set space. if (key) { assertUnfrozen('data', this.frozen) this.namespace = key return this } // Get space. return this.namespace } /** * 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. * New processors working the same way can be created by calling the * processor. * * It’s possible to freeze processors explicitly by calling `.freeze()`. * Processors freeze automatically when `.parse()`, `.run()`, `.runSync()`, * `.stringify()`, `.process()`, or `.processSync()` are called. * * @returns {Processor} * The current processor. */ freeze() { if (this.frozen) { return this } // Cast so that we can type plugins easier. // Plugins are supposed to be usable on different processors, not just on // this exact processor. const self = /** @type {Processor} */ (/** @type {unknown} */ (this)) while (++this.freezeIndex < this.attachers.length) { const [attacher, ...options] = this.attachers[this.freezeIndex] if (options[0] === false) { continue } if (options[0] === true) { options[0] = undefined } const transformer = attacher.call(self, ...options) if (typeof transformer === 'function') { this.transformers.use(transformer) } } this.frozen = true this.freezeIndex = Number.POSITIVE_INFINITY return this } /** * 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 {Compatible | undefined} [file] * file to parse (optional); typically `string` or `VFile`; any value * accepted as `x` in `new VFile(x)`. * @returns {ParseTree extends undefined ? Node : ParseTree} * Syntax tree representing `file`. */ parse(file) { this.freeze() const realFile = vfile(file) const parser = this.parser || this.Parser assertParser('parse', parser) return parser(String(realFile), realFile) } /** * Process the given file as configured on the processor. * * > 👉 **Note**: `process` freezes the processor if not already *frozen*. * * > 👉 **Note**: `process` performs the parse, run, and stringify phases. * * @overload * @param {Compatible | undefined} file * @param {ProcessCallback>} done * @returns {undefined} * * @overload * @param {Compatible | undefined} [file] * @returns {Promise>} * * @param {Compatible | undefined} [file] * File (optional); typically `string` or `VFile`]; any value accepted as * `x` in `new VFile(x)`. * @param {ProcessCallback> | undefined} [done] * Callback (optional). * @returns {Promise | undefined} * Nothing if `done` is given. * Otherwise a 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, done) { const self = this this.freeze() assertParser('process', this.parser || this.Parser) assertCompiler('process', this.compiler || this.Compiler) return done ? executor(undefined, done) : new Promise(executor) // Note: `void`s needed for TS. /** * @param {((file: VFileWithOutput) => undefined | void) | undefined} resolve * @param {(error: Error | undefined) => undefined | void} reject * @returns {undefined} */ function executor(resolve, reject) { const realFile = vfile(file) // Assume `ParseTree` (the result of the parser) matches `HeadTree` (the // input of the first transform). const parseTree = /** @type {HeadTree extends undefined ? Node : HeadTree} */ ( /** @type {unknown} */ (self.parse(realFile)) ) self.run(parseTree, realFile, function (error, tree, file) { if (error || !tree || !file) { return realDone(error) } // Assume `TailTree` (the output of the last transform) matches // `CompileTree` (the input of the compiler). const compileTree = /** @type {CompileTree extends undefined ? Node : CompileTree} */ ( /** @type {unknown} */ (tree) ) const compileResult = self.stringify(compileTree, file) if (looksLikeAValue(compileResult)) { file.value = compileResult } else { file.result = compileResult } realDone(error, /** @type {VFileWithOutput} */ (file)) }) /** * @param {Error | undefined} error * @param {VFileWithOutput | undefined} [file] * @returns {undefined} */ function realDone(error, file) { if (error || !file) { reject(error) } else if (resolve) { resolve(file) } else { assert(done, '`done` is defined if `resolve` is not') done(undefined, file) } } } } /** * Process the given file as configured on the processor. * * An error is thrown if asynchronous transforms are configured. * * > 👉 **Note**: `processSync` freezes the processor if not already *frozen*. * * > 👉 **Note**: `processSync` performs the parse, run, and stringify phases. * * @param {Compatible | undefined} [file] * File (optional); typically `string` or `VFile`; any value accepted as * `x` in `new VFile(x)`. * @returns {VFileWithOutput} * 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) { /** @type {boolean} */ let complete = false /** @type {VFileWithOutput | undefined} */ let result this.freeze() assertParser('processSync', this.parser || this.Parser) assertCompiler('processSync', this.compiler || this.Compiler) this.process(file, realDone) assertDone('processSync', 'process', complete) assert(result, 'we either bailed on an error or have a tree') return result /** * @type {ProcessCallback>} */ function realDone(error, file) { complete = true bail(error) result = file } } /** * Run *transformers* on a syntax tree. * * > 👉 **Note**: `run` freezes the processor if not already *frozen*. * * > 👉 **Note**: `run` performs the run phase, not other phases. * * @overload * @param {HeadTree extends undefined ? Node : HeadTree} tree * @param {RunCallback} done * @returns {undefined} * * @overload * @param {HeadTree extends undefined ? Node : HeadTree} tree * @param {Compatible | undefined} file * @param {RunCallback} done * @returns {undefined} * * @overload * @param {HeadTree extends undefined ? Node : HeadTree} tree * @param {Compatible | undefined} [file] * @returns {Promise} * * @param {HeadTree extends undefined ? Node : HeadTree} tree * Tree to transform and inspect. * @param {( * RunCallback | * Compatible * )} [file] * File associated with `node` (optional); any value accepted as `x` in * `new VFile(x)`. * @param {RunCallback} [done] * Callback (optional). * @returns {Promise | undefined} * Nothing if `done` is given. * Otherwise, a promise rejected with a fatal error or resolved with the * transformed tree. */ run(tree, file, done) { assertNode(tree) this.freeze() const transformers = this.transformers if (!done && typeof file === 'function') { done = file file = undefined } return done ? executor(undefined, done) : new Promise(executor) // Note: `void`s needed for TS. /** * @param {( * ((tree: TailTree extends undefined ? Node : TailTree) => undefined | void) | * undefined * )} resolve * @param {(error: Error) => undefined | void} reject * @returns {undefined} */ function executor(resolve, reject) { assert( typeof file !== 'function', '`file` can’t be a `done` anymore, we checked' ) const realFile = vfile(file) transformers.run(tree, realFile, realDone) /** * @param {Error | undefined} error * @param {Node} outputTree * @param {VFile} file * @returns {undefined} */ function realDone(error, outputTree, file) { const resultingTree = /** @type {TailTree extends undefined ? Node : TailTree} */ ( outputTree || tree ) if (error) { reject(error) } else if (resolve) { resolve(resultingTree) } else { assert(done, '`done` is defined if `resolve` is not') done(undefined, resultingTree, file) } } } } /** * 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 {HeadTree extends undefined ? Node : HeadTree} tree * Tree to transform and inspect. * @param {Compatible | undefined} [file] * File associated with `node` (optional); any value accepted as `x` in * `new VFile(x)`. * @returns {TailTree extends undefined ? Node : TailTree} * Transformed tree. */ runSync(tree, file) { /** @type {boolean} */ let complete = false /** @type {(TailTree extends undefined ? Node : TailTree) | undefined} */ let result this.run(tree, file, realDone) assertDone('runSync', 'run', complete) assert(result, 'we either bailed on an error or have a tree') return result /** * @type {RunCallback} */ function realDone(error, tree) { bail(error) result = tree complete = true } } /** * 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 {CompileTree extends undefined ? Node : CompileTree} tree * Tree to compile. * @param {Compatible | undefined} [file] * File associated with `node` (optional); any value accepted as `x` in * `new VFile(x)`. * @returns {CompileResult extends undefined ? Value : CompileResult} * 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(tree, file) { this.freeze() const realFile = vfile(file) const compiler = this.compiler || this.Compiler assertCompiler('stringify', compiler) assertNode(tree) return compiler(tree, realFile) } /** * Configure the processor to use a plugin, a list of usable values, or 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. * * > 👉 **Note**: `use` cannot be called on *frozen* processors. * > Call the processor first to create a new unfrozen processor. * * @example * There are many ways to pass plugins to `.use()`. * This example gives an overview: * * ```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}) * // Plugins: * .use([pluginB, pluginC]) * // Two plugins, the second with options: * .use([pluginD, [pluginE, {}]]) * // Preset with plugins and settings: * .use({plugins: [pluginF, [pluginG, {}]], settings: {position: false}}) * // Settings only: * .use({settings: {position: false}}) * ``` * * @template {Array} [Parameters=[]] * @template {Node | string | undefined} [Input=undefined] * @template [Output=Input] * * @overload * @param {Preset | null | undefined} [preset] * @returns {Processor} * * @overload * @param {PluggableList} list * @returns {Processor} * * @overload * @param {Plugin} plugin * @param {...(Parameters | [boolean])} parameters * @returns {UsePlugin} * * @param {PluggableList | Plugin | Preset | null | undefined} value * Usable value. * @param {...unknown} parameters * Parameters, when a plugin is given as a usable value. * @returns {Processor} * Current processor. */ use(value, ...parameters) { const attachers = this.attachers const namespace = this.namespace assertUnfrozen('use', this.frozen) if (value === null || value === undefined) { // Empty. } else if (typeof value === 'function') { addPlugin(value, parameters) } else if (typeof value === 'object') { if (Array.isArray(value)) { addList(value) } else { addPreset(value) } } else { throw new TypeError('Expected usable value, not `' + value + '`') } return this /** * @param {Pluggable} value * @returns {undefined} */ function add(value) { if (typeof value === 'function') { addPlugin(value, []) } else if (typeof value === 'object') { if (Array.isArray(value)) { const [plugin, ...parameters] = /** @type {PluginTuple>} */ (value) addPlugin(plugin, parameters) } else { addPreset(value) } } else { throw new TypeError('Expected usable value, not `' + value + '`') } } /** * @param {Preset} result * @returns {undefined} */ function addPreset(result) { if (!('plugins' in result) && !('settings' in result)) { throw new Error( 'Expected usable value but received an empty preset, which is probably a mistake: presets typically come with `plugins` and sometimes with `settings`, but this has neither' ) } addList(result.plugins) if (result.settings) { namespace.settings = extend(true, namespace.settings, result.settings) } } /** * @param {PluggableList | null | undefined} plugins * @returns {undefined} */ function addList(plugins) { let index = -1 if (plugins === null || plugins === undefined) { // Empty. } else if (Array.isArray(plugins)) { while (++index < plugins.length) { const thing = plugins[index] add(thing) } } else { throw new TypeError('Expected a list of plugins, not `' + plugins + '`') } } /** * @param {Plugin} plugin * @param {Array} parameters * @returns {undefined} */ function addPlugin(plugin, parameters) { let index = -1 let entryIndex = -1 while (++index < attachers.length) { if (attachers[index][0] === plugin) { entryIndex = index break } } if (entryIndex === -1) { attachers.push([plugin, ...parameters]) } // Only set if there was at least a `primary` value, otherwise we’d change // `arguments.length`. else if (parameters.length > 0) { let [primary, ...rest] = parameters const currentPrimary = attachers[entryIndex][1] if (isPlainObj(currentPrimary) && isPlainObj(primary)) { primary = extend(true, currentPrimary, primary) } attachers[entryIndex] = [plugin, primary, ...rest] } } } } // Note: this returns a *callable* instance. // That’s why it’s documented as a function. /** * Create a new processor. * * @example * This example shows how a new processor can be created (from `remark`) and linked * to **stdin**(4) and **stdout**(4). * * ```js * import process from 'node:process' * import concatStream from 'concat-stream' * import {remark} from 'remark' * * process.stdin.pipe( * concatStream(function (buf) { * process.stdout.write(String(remark().processSync(buf))) * }) * ) * ``` * * @returns * New *unfrozen* processor (`processor`). * * This processor 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. */ export const unified = new Processor().freeze() /** * Assert a parser is available. * * @param {string} name * @param {unknown} value * @returns {asserts value is Parser} */ function assertParser(name, value) { if (typeof value !== 'function') { throw new TypeError('Cannot `' + name + '` without `parser`') } } /** * Assert a compiler is available. * * @param {string} name * @param {unknown} value * @returns {asserts value is Compiler} */ function assertCompiler(name, value) { if (typeof value !== 'function') { throw new TypeError('Cannot `' + name + '` without `compiler`') } } /** * Assert the processor is not frozen. * * @param {string} name * @param {unknown} frozen * @returns {asserts frozen is false} */ function assertUnfrozen(name, frozen) { if (frozen) { throw new Error( 'Cannot call `' + name + '` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.' ) } } /** * Assert `node` is a unist node. * * @param {unknown} node * @returns {asserts node is Node} */ function assertNode(node) { // `isPlainObj` unfortunately uses `any` instead of `unknown`. // type-coverage:ignore-next-line if (!isPlainObj(node) || typeof node.type !== 'string') { throw new TypeError('Expected node, got `' + node + '`') // Fine. } } /** * Assert that `complete` is `true`. * * @param {string} name * @param {string} asyncName * @param {unknown} complete * @returns {asserts complete is true} */ function assertDone(name, asyncName, complete) { if (!complete) { throw new Error( '`' + name + '` finished async. Use `' + asyncName + '` instead' ) } } /** * @param {Compatible | undefined} [value] * @returns {VFile} */ function vfile(value) { return looksLikeAVFile(value) ? value : new VFile(value) } /** * @param {Compatible | undefined} [value] * @returns {value is VFile} */ function looksLikeAVFile(value) { return Boolean( value && typeof value === 'object' && 'message' in value && 'messages' in value ) } /** * @param {unknown} [value] * @returns {value is Value} */ function looksLikeAValue(value) { return typeof value === 'string' || isUint8Array(value) } /** * Assert `value` is an `Uint8Array`. * * @param {unknown} value * thing. * @returns {value is Uint8Array} * Whether `value` is an `Uint8Array`. */ function isUint8Array(value) { return Boolean( value && typeof value === 'object' && 'byteLength' in value && 'byteOffset' in value ) }