diff --git a/index.d.ts b/index.d.ts index e2d124a9..477f04f6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -import type {VFileValue} from 'vfile' +import type {Value} from 'vfile' import type {CompileResults} from './lib/index.js' export type { @@ -8,13 +8,13 @@ export type { CompilerClass, CompilerFunction, // `Data` is typed and exposed below. + Parser, + ParserClass, + ParserFunction, Pluggable, PluggableList, Plugin, PluginTuple, - Parser, - ParserClass, - ParserFunction, Preset, ProcessCallback, Processor, @@ -29,7 +29,7 @@ export {unified} from './lib/index.js' /** * Interface of known results from compilers. * - * Normally, compilers result in text ({@link VFileValue `VFileValue`}). + * Normally, compilers result in text ({@link Value `Value`} of `vfile`). * When you compile to something else, such as a React node (as in, * `rehype-react`), you can augment this interface to include that type. * diff --git a/index.test-d.ts b/index.test-d.ts index 8a3f45f7..05a5a226 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -477,6 +477,10 @@ declare module './index.js' { interface CompileResultMap { ReactNode: ReactNode } + + interface Data { + something?: string | undefined + } } // Compile plugin (to a non-node). diff --git a/lib/index.js b/lib/index.js index 9dbeec78..f0072c5f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,8 +3,8 @@ * * @typedef {import('unist').Node} Node * - * @typedef {import('vfile').VFileCompatible} VFileCompatible - * @typedef {import('vfile').VFileValue} VFileValue + * @typedef {import('vfile').Compatible} Compatible + * @typedef {import('vfile').Value} Value * * @typedef {import('../index.js').CompileResultMap} CompileResultMap * @typedef {import('../index.js').Data} Data @@ -31,12 +31,12 @@ * 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`). + * textual representation of the given tree (typically `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`. + * and typically returns a `string`. * * > πŸ‘‰ **Note**: unified typically compiles by serializing: most compilers * > return `string` (or `Uint8Array`). @@ -159,38 +159,35 @@ * * 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 + * (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 - * **Plugins** configure the processors they are applied on in the following + * 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 * - * 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. + * In practise, they are functions that can receive options and configure the + * processor (`this`). * - * > πŸ‘‰ **Note**: attachers are called when the processor is *frozen*, - * > not when they are applied. + * > πŸ‘‰ **Note**: plugins are called when the processor is *frozen*, not when + * > they are applied. */ /** - * Tuple of a plugin and its setting(s). + * Tuple of a plugin and its configuration. + * * The first item is a plugin, the rest are its parameters. * * @template {Array} [TupleParameters=[]] @@ -205,12 +202,6 @@ * * If the plugin sets a {@link Compiler `Compiler`}, this should be the * node it expects. * @template [Output=undefined] (optional). - * @typedef {( - * [ - * plugin: Plugin, - * ...parameters: TupleParameters - * ] - * )} PluginTuple * Value that is yielded as output. * * * If the plugin returns a {@link Transformer `Transformer`}, this @@ -219,16 +210,22 @@ * 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 - * Presets are sharable configuration. + * Sharable configuration. * * They can contain plugins and settings. * @property {PluggableList | undefined} [plugins] * List of plugins and presets (optional). - * @property {Record | undefined} [settings] + * @property {Data | undefined} [settings] * Shared settings for parsers and compilers (optional). */ @@ -264,6 +261,24 @@ * 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`). @@ -280,6 +295,10 @@ * 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. @@ -288,12 +307,12 @@ * @param {TransformCallback} next * Callback. * @returns {( - * Promise | - * Promise | // For some reason this is needed separately. - * Output | - * Error | - * undefined | - * void + * Promise | + * Promise | // For some reason this is needed separately. + * Output | + * Error | + * undefined | + * void * )} * If you accept `next`, nothing. * Otherwise: @@ -304,22 +323,6 @@ * * `Promise` or `Node` β€” new, changed, tree */ -/** - * @template {Node} [Output=Node] - * Node type that the transformer yields (default: `Node`). - * @callback TransformCallback - * If the signature of a `transformer` accepts a third argument, the - * transformer may perform asynchronous operations, and must call `next()`. - * @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 | undefined} ParseTree * Output of `parse`. @@ -382,7 +385,7 @@ * @template {CompileResults | undefined} Result * Node type that the transformer yields. * @typedef {( - * Result extends VFileValue | undefined ? + * Result extends Value | undefined ? * VFile : * VFile & {result: Result} * )} VFileWithOutput @@ -400,6 +403,14 @@ import {trough} from 'trough' import {VFile} from 'vfile' import {CallableInstance} from './callable-instance.js' +// To do: we could drop class support for the parser and compiler, +// we don’t use that anymore? +// Would be less breaking if we do `processor.compiler || processor.Compiler`. + +// 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 /** @@ -424,33 +435,7 @@ export class Processor extends CallableInstance { super('copy') /** - * 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 to use. * * @type {( * Compiler< @@ -463,18 +448,7 @@ export class Processor extends CallableInstance { this.Compiler = 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 to use. * * @type {( * Parser | @@ -574,6 +548,26 @@ export class Processor extends CallableInstance { * > πŸ‘‰ **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 @@ -598,8 +592,8 @@ export class Processor extends CallableInstance { * @param {Data[Key]} [value] * Value to set (optional). * @returns {unknown} - * The processor that `data` is called on when settings, the value at `key` - * when getting, or the entire dataset when getting w/o key. + * 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') { @@ -640,7 +634,7 @@ export class Processor extends CallableInstance { * `.stringify()`, `.process()`, or `.processSync()` are called. * * @returns {Processor} - * The processor that `freeze` was called on. + * The current processor. */ freeze() { if (this.frozen) { @@ -684,9 +678,9 @@ export class Processor extends CallableInstance { * > πŸ‘‰ **Note**: `parse` performs the parse phase, not the run phase or other * > phases. * - * @param {VFileCompatible | undefined} [file] - * file to parse (optional); typically `string`; any value accepted as `x` - * in `new VFile(x)`. + * @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`. */ @@ -723,21 +717,22 @@ export class Processor extends CallableInstance { * > πŸ‘‰ **Note**: `process` performs the parse, run, and stringify phases. * * @overload - * @param {VFileCompatible | undefined} file + * @param {Compatible | undefined} file * @param {ProcessCallback>} done * @returns {undefined} * * @overload - * @param {VFileCompatible | undefined} [file] + * @param {Compatible | undefined} [file] * @returns {Promise>} * - * @param {VFileCompatible | undefined} [file] - * File (optional); any value accepted as `x` in `new VFile(x)`. + * @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 `Promise`, rejected with a fatal error or resolved with the + * Otherwise a promise, rejected with a fatal error or resolved with the * processed file. * * The parsed, transformed, and compiled value is available at @@ -794,7 +789,7 @@ export class Processor extends CallableInstance { const compileResult = self.stringify(compileTree, file) - if (looksLikeAVFileValue(compileResult)) { + if (looksLikeAValue(compileResult)) { file.value = compileResult } else { file.result = compileResult @@ -830,8 +825,9 @@ export class Processor extends CallableInstance { * * > πŸ‘‰ **Note**: `processSync` performs the parse, run, and stringify phases. * - * @param {VFileCompatible | undefined} [file] - * File (optional); any value accepted as `x` in `new VFile(x)`. + * @param {Compatible | undefined} [file] + * File (optional); typically `string` or `VFile`; any value accepted as + * `x` in `new VFile(x)`. * @returns {VFileWithOutput} * The processed file. * @@ -891,20 +887,20 @@ export class Processor extends CallableInstance { * * @overload * @param {HeadTree extends undefined ? Node : HeadTree} tree - * @param {VFileCompatible | undefined} file + * @param {Compatible | undefined} file * @param {RunCallback} done * @returns {undefined} * * @overload * @param {HeadTree extends undefined ? Node : HeadTree} tree - * @param {VFileCompatible | undefined} [file] + * @param {Compatible | undefined} [file] * @returns {Promise} * * @param {HeadTree extends undefined ? Node : HeadTree} tree * Tree to transform and inspect. * @param {( * RunCallback | - * VFileCompatible + * Compatible * )} [file] * File associated with `node` (optional); any value accepted as `x` in * `new VFile(x)`. @@ -912,7 +908,7 @@ export class Processor extends CallableInstance { * Callback (optional). * @returns {Promise | undefined} * Nothing if `done` is given. - * Otherwise, `Promise` rejected with a fatal error or resolved with the + * Otherwise, a promise rejected with a fatal error or resolved with the * transformed tree. */ run(tree, file, done) { @@ -980,7 +976,7 @@ export class Processor extends CallableInstance { * * @param {HeadTree extends undefined ? Node : HeadTree} tree * Tree to transform and inspect. - * @param {VFileCompatible | undefined} [file] + * @param {Compatible | undefined} [file] * File associated with `node` (optional); any value accepted as `x` in * `new VFile(x)`. * @returns {TailTree extends undefined ? Node : TailTree} @@ -1014,14 +1010,14 @@ export class Processor extends CallableInstance { * > πŸ‘‰ **Note**: `stringify` freezes the processor if not already *frozen*. * * > πŸ‘‰ **Note**: `stringify` performs the stringify phase, not the run phase - * or other phases. + * > or other phases. * * @param {CompileTree extends undefined ? Node : CompileTree} tree - * Tree to compile - * @param {VFileCompatible | undefined} [file] + * 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 ? VFileValue : CompileResult} + * @returns {CompileResult extends undefined ? Value : CompileResult} * Textual representation of the tree (see note). * * > πŸ‘‰ **Note**: unified typically compiles by serializing: most compilers @@ -1072,14 +1068,20 @@ export class Processor extends CallableInstance { } /** - * Configure the processor to use a plugin, a list of usable - * values, or with a preset. + * 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' * @@ -1238,8 +1240,33 @@ export class Processor extends CallableInstance { } } +// Note: this returns a *callable* instance. +// That’s why it’s documented as a function. /** - * Base processor. + * 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() @@ -1300,7 +1327,7 @@ function assertParser(name, value) { * * @param {string} name * @param {unknown} value - * @returns {asserts value is Compiler} + * @returns {asserts value is Compiler} */ function assertCompiler(name, value) { if (typeof value !== 'function') { @@ -1357,7 +1384,7 @@ function assertDone(name, asyncName, complete) { } /** - * @param {VFileCompatible | undefined} [value] + * @param {Compatible | undefined} [value] * @returns {VFile} */ function vfile(value) { @@ -1365,7 +1392,7 @@ function vfile(value) { } /** - * @param {VFileCompatible | undefined} [value] + * @param {Compatible | undefined} [value] * @returns {value is VFile} */ function looksLikeAVFile(value) { @@ -1379,9 +1406,9 @@ function looksLikeAVFile(value) { /** * @param {unknown} [value] - * @returns {value is VFileValue} + * @returns {value is Value} */ -function looksLikeAVFileValue(value) { +function looksLikeAValue(value) { return typeof value === 'string' || isUint8Array(value) } diff --git a/readme.md b/readme.md index ce24b8ae..d727415f 100644 --- a/readme.md +++ b/readme.md @@ -19,19 +19,37 @@ * [Overview](#overview) * [API](#api) * [`processor()`](#processor) - * [`processor.use(plugin[, options])`](#processoruseplugin-options) + * [`processor.Compiler`](#processorcompiler) + * [`processor.Parser`](#processorparser) + * [`processor.data([key[, value]])`](#processordatakey-value) + * [`processor.freeze()`](#processorfreeze) * [`processor.parse(file)`](#processorparsefile) - * [`processor.stringify(tree[, file])`](#processorstringifytree-file) - * [`processor.run(tree[, file][, done])`](#processorruntree-file-done) - * [`processor.runSync(tree[, file])`](#processorrunsynctree-file) * [`processor.process(file[, done])`](#processorprocessfile-done) * [`processor.processSync(file)`](#processorprocesssyncfile) - * [`processor.data([key[, value]])`](#processordatakey-value) - * [`processor.freeze()`](#processorfreeze) -* [`Plugin`](#plugin) - * [`function attacher(options?)`](#function-attacheroptions) - * [`function transformer(tree, file[, next])`](#function-transformertree-file-next) -* [`Preset`](#preset) + * [`processor.run(tree[, file][, done])`](#processorruntree-file-done) + * [`processor.runSync(tree[, file])`](#processorrunsynctree-file) + * [`processor.stringify(tree[, file])`](#processorstringifytree-file) + * [`processor.use(plugin[, options])`](#processoruseplugin-options) + * [`CompileResultMap`](#compileresultmap) + * [`CompileResults`](#compileresults) + * [`Compiler`](#compiler) + * [`CompilerClass`](#compilerclass) + * [`CompilerFunction`](#compilerfunction) + * [`Data`](#data) + * [`Parser`](#parser) + * [`ParserClass`](#parserclass) + * [`ParserFunction`](#parserfunction) + * [`Pluggable`](#pluggable) + * [`PluggableList`](#pluggablelist) + * [`Plugin`](#plugin) + * [`PluginTuple`](#plugintuple) + * [`Preset`](#preset) + * [`ProcessCallback`](#processcallback) + * [`Processor`](#processor-1) + * [`RunCallback`](#runcallback) + * [`Settings`](#settings) + * [`TransformCallback`](#transformcallback) + * [`Transformer`](#transformer) * [Types](#types) * [Compatibility](#compatibility) * [Contribute](#contribute) @@ -45,7 +63,7 @@ unified is two things: * **unified** is a collective of 500+ free and open source packages that work with content as structured data (ASTs) -* `unified` (this project) is the core package, used in 800k+ projects on GH, +* `unified` (this project) is the core package, used in 1.3m+ projects on GH, to process content with plugins Several ecosystems are built on unified around different kinds of content. @@ -72,8 +90,8 @@ generator. You can connect utilities together and make your own plugins that check for problems and transform from one thing to another. -When you are dealing with one type of content (such as markdown), it’s -recommended to use the main package of that ecosystem instead (so `remark`). +When you are dealing with one type of content (such as markdown), you can use +the main package of that ecosystem instead (so `remark`). When you are dealing with different kinds of content (such as markdown and HTML), it’s recommended to use `unified` itself, and pick and choose the plugins you need. @@ -81,7 +99,7 @@ you need. ## Install This package is [ESM only][esm]. -In Node.js (version 12.20+, 14.14+, or 16.0+), install with [npm][]: +In Node.js (version 16+), install with [npm][]: ```sh npm install unified @@ -104,12 +122,12 @@ In browsers with [`esm.sh`][esmsh]: ## Use ```js -import {unified} from 'unified' -import remarkParse from 'remark-parse' -import remarkRehype from 'remark-rehype' import rehypeDocument from 'rehype-document' import rehypeFormat from 'rehype-format' import rehypeStringify from 'rehype-stringify' +import remarkParse from 'remark-parse' +import remarkRehype from 'remark-rehype' +import {unified} from 'unified' import {reporter} from 'vfile-reporter' const file = await unified() @@ -152,11 +170,11 @@ no issues found `unified` is an interface for processing content with syntax trees. Syntax trees are a representation of content understandable to programs. -Those programs, called *[plugins][plugin]*, take these trees and inspect and +Those programs, called *[plugins][api-plugin]*, take these trees and inspect and modify them. -To get to the syntax tree from text, there is a *[parser][]*. -To get from that back to text, there is a *[compiler][]*. -This is the *[process][]* of a *processor*. +To get to the syntax tree from text, there is a *[parser][api-parser]*. +To get from that back to text, there is a *[compiler][api-compiler]*. +This is the *[process][api-process]* of a *processor*. ```ascii | ........................ process ........................... | @@ -205,8 +223,8 @@ the ancestral processor. When processors are exposed from a module (for example, `unified` itself) they should not be configured directly, as that would change their behavior for all module users. -Those processors are *[frozen][freeze]* and they should be called to create a -new processor before they are used. +Those processors are *[frozen][api-freeze]* and they should be called to create +a new processor before they are used. ###### File @@ -279,8 +297,8 @@ There are also a few plugins that work in any ecosystem: ###### Configuration -Processors are configured with [plugins][plugin] or with the [`data`][data] -method. +Processors are configured with [plugins][api-plugin] or with the +[`data`][api-data] method. Most plugins also accept configuration through options. See each plugin’s readme for more info. @@ -299,14 +317,14 @@ The [API][] provided by `unified` allows multiple files to be processed and gives access to metadata (such as lint messages): ```js -import {unified} from 'unified' +import rehypeStringify from 'rehype-stringify' import remarkParse from 'remark-parse' import remarkPresetLintMarkdownStyleGuide from 'remark-preset-lint-markdown-style-guide' +import remarkRehype from 'remark-rehype' import remarkRetext from 'remark-retext' import retextEnglish from 'retext-english' import retextEquality from 'retext-equality' -import remarkRehype from 'remark-rehype' -import rehypeStringify from 'rehype-stringify' +import {unified} from 'unified' import {reporter} from 'vfile-reporter' const file = await unified() @@ -324,8 +342,8 @@ console.log(String(file)) Yields: ```txt - 1:16-1:24 warning Emphasis should use `*` as a marker emphasis-marker remark-lint - 1:30-1:34 warning `guys` may be insensitive, use `people`, `persons`, `folks` instead gals-man retext-equality +1:16-1:24 warning Emphasis should use `*` as a marker emphasis-marker remark-lint +1:30-1:34 warning `guys` may be insensitive, use `people`, `persons`, `folks` instead gals-man retext-equality ⚠ 2 warnings ``` @@ -369,14 +387,15 @@ There is no default export. ### `processor()` -Create a processor. +Create a new processor. ###### Returns -New *[unfrozen][freeze]* 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. +New *[unfrozen][api-freeze]* processor ([`processor`][api-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. ###### Example @@ -395,73 +414,152 @@ process.stdin.pipe( ) ``` -### `processor.use(plugin[, options])` +### `processor.Compiler` -Configure the processor to use a plugin and optionally configure that plugin -with options. +Compiler to use ([`Compiler`][api-compiler], optional). -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. +### `processor.Parser` + +Parser to use ([`Parser`][api-parser], optional). + +### `processor.data([key[, value]])` + +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][overview]. -> πŸ‘‰ **Note**: `use` cannot be called on *[frozen][freeze]* processors. +> πŸ‘‰ **Note**: setting information cannot occur on *[frozen][api-freeze]* +> processors. > Call the processor first to create a new unfrozen processor. +> πŸ‘‰ **Note**: to register custom data in TypeScript, augment the +> [`Data`][api-data] interface. + ###### Signatures -* `processor.use(plugin[, options])` -* `processor.use(preset)` -* `processor.use(list)` +* `processor = processor.data(key, value)` +* `processor = processor.data(dataset)` +* `value = processor.data(key)` +* `dataset = processor.data()` ###### Parameters -* `plugin` ([`Attacher`][plugin]) -* `options` (`*`, optional) β€” configuration for `plugin` -* `preset` (`Object`) β€” object with an optional `plugins` (set to `list`), - and/or an optional `settings` object -* `list` (`Array`) β€” list of plugins, presets, and pairs (`plugin` and - `options` in an array) +* `key` ([`keyof Data`][api-data], optional) β€” field to get +* `value` ([`Data[key]`][api-data]) β€” value to set +* `values` ([`Data`][api-data]) β€” values to set ###### Returns -The processor that `use` was called on (`processor`). +The current processor when setting ([`processor`][api-processor]), the value at +`key` when getting ([`Data[key]`][api-data]), or the entire dataset when +getting without key ([`Data`][api-data]). ###### Example -There are many ways to pass plugins to `.use()`. -This example gives an overview: +This example show how to get and set info: ```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}}) +const processor = unified().data('alpha', 'bravo') + +processor.data('alpha') // => 'bravo' + +processor.data() // => {alpha: 'bravo'} + +processor.data({charlie: 'delta'}) + +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. +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 + +The current processor ([`processor`][api-processor]). + +###### Example + +This example, `index.js`, shows how `rehype` prevents extensions to itself: + +```js +import rehypeParse from 'rehype-parse' +import rehypeStringify from 'rehype-stringify' +import {unified} from 'unified' + +export const rehype = unified().use(rehypeParse).use(rehypeStringify).freeze() +``` + +That processor can be used and configured like so: + +```js +import {rehype} from 'rehype' +import rehypeFormat from 'rehype-format' +// … + +rehype() + .use(rehypeFormat) + // … +``` + +A similar looking example is broken as operates on the frozen interface. +If this behavior was allowed it would result in unexpected behavior so an error +is thrown. +**This is not valid**: + +```js +import {rehype} from 'rehype' +import rehypeFormat from 'rehype-format' +// … + +rehype + .use(rehypeFormat) + // … +``` + +Yields: + +```txt +~/node_modules/unified/index.js:426 + throw new Error( + ^ + +Error: Cannot call `use` on a frozen processor. +Create a new processor first, by calling it: use `processor()` instead of `processor`. + at assertUnfrozen (~/node_modules/unified/index.js:426:11) + at Function.use (~/node_modules/unified/index.js:165:5) + … ``` ### `processor.parse(file)` Parse text to a syntax tree. -> πŸ‘‰ **Note**: `parse` freezes the processor if not already *[frozen][freeze]*. +> πŸ‘‰ **Note**: `parse` freezes the processor if not already +> *[frozen][api-freeze]*. > πŸ‘‰ **Note**: `parse` performs the [parse phase][overview], not the run phase > or other phases. ###### Parameters -* `file` ([`VFile`][vfile]) β€” file to parse; typically `string`; any value - accepted as `x` in `new VFile(x)` +* `file` ([`Compatible`][vfile-compatible]) β€” file to parse; typically + `string` or [`VFile`][vfile]; any value accepted as `x` in `new VFile(x)` ###### Returns @@ -472,8 +570,8 @@ Syntax tree representing `file` ([`Node`][node]). This example shows how `parse` can be used to create a tree from a file. ```js -import {unified} from 'unified' import remarkParse from 'remark-parse' +import {unified} from 'unified' const tree = unified().use(remarkParse).parse('# Hello world!') @@ -486,7 +584,7 @@ Yields: { type: 'root', children: [ - {type: 'heading', depth: 1, children: [Array], position: [Position]} + {type: 'heading', depth: 1, children: [Array], position: [Object]} ], position: { start: {line: 1, column: 1, offset: 0}, @@ -495,125 +593,195 @@ 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]). +### `processor.process(file[, done])` -`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]. +Process the given file as configured on the processor. -### `processor.stringify(tree[, file])` +> πŸ‘‰ **Note**: `process` freezes the processor if not already +> *[frozen][api-freeze]*. -Compile a syntax tree. +> πŸ‘‰ **Note**: `process` performs the [parse, run, and stringify +> phases][overview]. -> πŸ‘‰ **Note**: `stringify` freezes the processor if not already -> *[frozen][freeze]*. +###### Signatures -> πŸ‘‰ **Note**: `stringify` performs the [stringify phase][overview], not the run -> phase or other phases. +* `processor.process(file, done)` +* `Promise = processor.process(file?)` ###### Parameters -* `tree` ([`Node`][node]) β€” tree to compile -* `file` ([`VFile`][vfile], optional) β€” file associated with `node`; any - value accepted as `x` in `new VFile(x)` +* `file` ([`Compatible`][vfile-compatible], optional) β€” file; typically + `string` or [`VFile`][vfile]; any value accepted as `x` in `new VFile(x)` +* `done` ([`ProcessCallback`][api-process-callback], optional) β€” callback ###### Returns -Textual representation of the tree (`string` or `Uint8Array`, see note). +Nothing if `done` is given (`undefined`). +Otherwise a promise, rejected with a fatal error or resolved with the +processed file ([`Promise`][vfile]). + +The parsed, transformed, and compiled value is available at `file.value` (see +note). > πŸ‘‰ **Note**: unified typically compiles by serializing: most -> [compilers][compiler] return `string` (or `Uint8Array`). +> 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 +> [`CompileResultMap`][api-compile-result-map]. ###### Example -This example shows how `stringify` can be used to serialize a syntax tree: +This example shows how `process` can be used to process a file: ```js -import {unified} from 'unified' +import rehypeDocument from 'rehype-document' +import rehypeFormat from 'rehype-format' import rehypeStringify from 'rehype-stringify' -import {h} from 'hastscript' - -const tree = h('h1', 'Hello world!') +import remarkParse from 'remark-parse' +import remarkRehype from 'remark-rehype' +import {unified} from 'unified' -const doc = unified().use(rehypeStringify).stringify(tree) +const file = await unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeDocument, {title: 'πŸ‘‹πŸŒ'}) + .use(rehypeFormat) + .use(rehypeStringify) + .process('# Hello world!') -console.log(doc) +console.log(String(file)) ``` Yields: ```html -

Hello world!

+ + + + + πŸ‘‹πŸŒ + + + +

Hello world!

+ + ``` -#### `processor.Compiler` +### `processor.processSync(file)` -A **compiler** handles the compiling of a syntax tree to something else (in -most cases, text). +Process the given file as configured on the processor. -It is used in the [stringify phase][overview] and called with a [`Node`][node] -and [`VFile`][file] representation of the document to compile. +An error is thrown if asynchronous transforms are configured. -`Compiler` can be a normal function, in which case it should return the textual -representation of the given tree (`string`). +> πŸ‘‰ **Note**: `processSync` freezes the processor if not already +> *[frozen][api-freeze]*. -`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**: `processSync` performs the [parse, run, and stringify +> phases][overview]. -> πŸ‘‰ **Note**: unified typically compiles by serializing: most compilers -> return `string` (or `Uint8Array`). +###### Parameters + +* `file` ([`Compatible`][vfile-compatible], optional) β€” file; typically + `string` or [`VFile`][vfile]; any value accepted as `x` in `new VFile(x)` + +###### Returns + +The processed file ([`VFile`][vfile]). + +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 +> [`CompileResultMap`][api-compile-result-map]. + +###### Example + +This example shows how `processSync` can be used to process a file, if all +transformers are synchronous. + +```js +import rehypeDocument from 'rehype-document' +import rehypeFormat from 'rehype-format' +import rehypeStringify from 'rehype-stringify' +import remarkParse from 'remark-parse' +import remarkRehype from 'remark-rehype' +import {unified} from 'unified' + +const processor = unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeDocument, {title: 'πŸ‘‹πŸŒ'}) + .use(rehypeFormat) + .use(rehypeStringify) + +console.log(String(processor.processSync('# Hello world!'))) +``` + +Yields: + +```html + + + + + πŸ‘‹πŸŒ + + + +

Hello world!

+ + +``` ### `processor.run(tree[, file][, done])` -Run *[transformers][transformer]* on a syntax tree. +Run *[transformers][api-transformer]* on a syntax tree. -> πŸ‘‰ **Note**: `run` freezes the processor if not already *[frozen][freeze]*. +> πŸ‘‰ **Note**: `run` freezes the processor if not already +> *[frozen][api-freeze]*. > πŸ‘‰ **Note**: `run` performs the [run phase][overview], not other phases. +###### Signatures + +* `processor.run(tree, done)` +* `processor.run(tree, file, done)` +* `Promise = processor.run(tree, file?)` + ###### Parameters * `tree` ([`Node`][node]) β€” tree to transform and inspect -* `file` ([`VFile`][vfile], optional) β€” any value accepted as `x` in - `new VFile(x)` -* `done` ([`Function`][run-done], optional) β€” callback +* `file` ([`Compatible`][vfile-compatible], optional) β€” file associated + with `node`; any value accepted as `x` in `new VFile(x)` +* `done` ([`RunCallback`][api-run-callback], optional) β€” callback ###### Returns 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]). +Otherwise, a promise rejected with a fatal error or resolved with the +transformed tree ([`Promise`][node]). ###### Example This example shows how `run` can be used to transform a tree: ```js -import {unified} from 'unified' import remarkReferenceLinks from 'remark-reference-links' +import {unified} from 'unified' import {u} from 'unist-builder' const tree = u('root', [ @@ -634,361 +802,405 @@ Yields: type: 'root', children: [ {type: 'paragraph', children: [Array]}, - {type: 'definition', identifier: '1', title: undefined, url: undefined} + {type: 'definition', identifier: '1', title: '', url: undefined} ] } ``` -#### `function done(err[, tree, file])` +### `processor.runSync(tree[, file])` -Callback called when transformers are done. +Run *[transformers][api-transformer]* on a syntax tree. -Called with either an error or results. - -###### Parameters - -* `err` (`Error`, optional) β€” fatal error -* `tree` ([`Node`][node], optional) β€” transformed tree -* `file` ([`VFile`][vfile], optional) β€” file - -### `processor.runSync(tree[, file])` - -Run *[transformers][transformer]* on a syntax tree. An error is thrown if asynchronous transforms are configured. > πŸ‘‰ **Note**: `runSync` freezes the processor if not already -> *[frozen][freeze]*. +> *[frozen][api-freeze]*. > πŸ‘‰ **Note**: `runSync` performs the [run phase][overview], not other phases. ###### Parameters * `tree` ([`Node`][node]) β€” tree to transform and inspect -* `file` ([`VFile`][vfile], optional) β€” any value accepted as `x` in - `new VFile(x)` +* `file` ([`Compatible`][vfile-compatible], optional) β€” file associated + with `node`; any value accepted as `x` in `new VFile(x)` ###### Returns Transformed tree ([`Node`][node]). -### `processor.process(file[, done])` +### `processor.stringify(tree[, file])` -Process the given file as configured on the processor. +Compile a syntax tree. -> πŸ‘‰ **Note**: `process` freezes the processor if not already -> *[frozen][freeze]*. +> πŸ‘‰ **Note**: `stringify` freezes the processor if not already +> *[frozen][api-freeze]*. -> πŸ‘‰ **Note**: `process` performs the [parse, run, and stringify -> phases][overview]. +> πŸ‘‰ **Note**: `stringify` performs the [stringify phase][overview], not the run +> phase or other phases. ###### Parameters -* `file` ([`VFile`][vfile]) β€” file; any value accepted as `x` in - `new VFile(x)` -* `done` ([`Function`][process-done], optional) β€” callback +* `tree` ([`Node`][node]) β€” tree to compile +* `file` ([`Compatible`][vfile-compatible], optional) β€” file associated + with `node`; any value accepted as `x` in `new VFile(x)` ###### Returns -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]). - -The parsed, transformed, and compiled value is available at -[`file.value`][vfile-value] (see note). +Textual representation of the tree (`Uint8Array` or `string`, see note). -> πŸ‘‰ **Note**: unified typically compiles by serializing: most -> [compilers][compiler] return `string` (or `Uint8Array`). +> πŸ‘‰ **Note**: unified typically compiles by serializing: most compilers +> return `string` (or `Uint8Array`). > Some compilers, such as the one configured with -> [`rehype-react`][rehype-react], result in other values (in this case, a React -> tree). -> If you’re using a compiler that does not serialize, the result is available -> at `file.result`. +> [`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 +> [`CompileResultMap`][api-compile-result-map]. ###### Example -This example shows how `process` can be used to process a file: +This example shows how `stringify` can be used to serialize a syntax tree: ```js -import {unified} from 'unified' -import remarkParse from 'remark-parse' -import remarkRehype from 'remark-rehype' -import rehypeDocument from 'rehype-document' -import rehypeFormat from 'rehype-format' +import {h} from 'hastscript' import rehypeStringify from 'rehype-stringify' +import {unified} from 'unified' -const file = await unified() - .use(remarkParse) - .use(remarkRehype) - .use(rehypeDocument, {title: 'πŸ‘‹πŸŒ'}) - .use(rehypeFormat) - .use(rehypeStringify) - .process('# Hello world!') +const tree = h('h1', 'Hello world!') -console.log(String(file)) +const doc = unified().use(rehypeStringify).stringify(tree) + +console.log(doc) ``` Yields: ```html - - - - - πŸ‘‹πŸŒ - - - -

Hello world!

- - +

Hello world!

``` -#### `function done(err, file)` +### `processor.use(plugin[, options])` -Callback called when the process is done. +Configure the processor to use a plugin, a list of usable values, or a preset. -Called with either an error or a result. +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*][api-freeze] processors. +> Call the processor first to create a new unfrozen processor. + +###### Signatures + +* `processor.use(preset?)` +* `processor.use(list)` +* `processor.use(plugin[, ...parameters])` ###### Parameters -* `err` (`Error`, optional) β€” fatal error -* `file` ([`VFile`][vfile]) β€” processed file +* `preset` ([`Preset`][api-preset]) β€” plugins and settings +* `list` ([`PluggableList`][api-pluggable-list]) β€” list of usable things +* `plugin` ([`Plugin`][api-plugin]) β€” plugin +* `parameters` (`Array`) β€” configuration for `plugin`, typically a + single options object + +###### Returns + +Current processor ([`processor`][api-processor]). ###### Example -This example shows how `process` can be used to process a file with a callback. +There are many ways to pass plugins to `.use()`. +This example gives an overview: ```js import {unified} from 'unified' -import remarkParse from 'remark-parse' -import remarkGithub from 'remark-github' -import remarkStringify from 'remark-stringify' -import {reporter} from 'vfile-reporter' unified() - .use(remarkParse) - .use(remarkGithub) - .use(remarkStringify) - .process('@unifiedjs', function (error, file) { - console.error(reporter(error || file)) - if (file) { - console.log(String(file)) - } - }) + // 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}}) ``` -Yields: +### `CompileResultMap` -```txt -no issues found +Interface of known results from compilers (TypeScript type). + +Normally, compilers result in text ([`Value`][vfile-value] of `vfile`). +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 + } +} + +export {} // You may not need this, but it makes sure the file is a module. ``` -```markdown -[**@unifiedjs**](https://github.com/unifiedjs) +Use [`CompileResults`][api-compile-results] to access the values. + +###### Type + +```ts +interface CompileResultMap { + // Note: if `Value` from `VFile` is changed, this should too. + Uint8Array: Uint8Array + string: string +} ``` -### `processor.processSync(file)` +### `CompileResults` -Process the given file as configured on the processor. -An error is thrown if asynchronous transforms are configured. +Acceptable results from compilers (TypeScript type). -> πŸ‘‰ **Note**: `processSync` freezes the processor if not already -> *[frozen][freeze]*. +To register custom results, add them to +[`CompileResultMap`][api-compile-result-map]. -> πŸ‘‰ **Note**: `processSync` performs the [parse, run, and stringify -> phases][overview]. +###### Type -###### Parameters +```ts +type CompileResults = CompileResultMap[keyof CompileResultMap] +``` -* `file` ([`VFile`][vfile]) β€” any value accepted as `x` in `new VFile(x)` +### `Compiler` -###### Returns +A **compiler** handles the compiling of a syntax tree to something else +(in most cases, text) (TypeScript type). -The processed file ([`VFile`][vfile]). +It is used in the stringify phase and called with a [`Node`][node] +and [`VFile`][vfile] representation of the document to compile. -The parsed, transformed, and compiled value is available at -[`file.value`][vfile-value] (see note). +`Compiler` can be a normal function, in which case it should return the +textual representation of the given tree (typically `string`). -> πŸ‘‰ **Note**: unified typically compiles by serializing: most -> [compilers][compiler] return `string` (or `Uint8Array`). +`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 typically returns 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], result in other values (in this case, a React -> tree). -> If you’re using a compiler that does not serialize, the result is available -> at `file.result`. +> [`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 +> [`CompileResultMap`][api-compile-result-map]. + +###### Type + +```ts +type Compiler< + Tree extends Node = Node, + Result extends CompileResults = CompileResults +> = CompilerClass | CompilerFunction +``` -###### Example +See [`CompilerClass`][api-compiler-class] and +[`CompilerFunction`][api-compiler-function] for more info. -This example shows how `processSync` can be used to process a file, if all -transformers are synchronous. +### `CompilerClass` -```js -import {unified} from 'unified' -import remarkParse from 'remark-parse' -import remarkRehype from 'remark-rehype' -import rehypeDocument from 'rehype-document' -import rehypeFormat from 'rehype-format' -import rehypeStringify from 'rehype-stringify' +Class to compile trees (TypeScript type). -const processor = unified() - .use(remarkParse) - .use(remarkRehype) - .use(rehypeDocument, {title: 'πŸ‘‹πŸŒ'}) - .use(rehypeFormat) - .use(rehypeStringify) +###### Type -console.log(String(processor.processSync('# Hello world!'))) +```ts +type CompilerClass< + Tree extends Node = Node, + Result extends CompileResults = CompileResults +> = { + new (tree: Tree, file: VFile): CompilerClass['prototype'] + prototype: { + compile(): Result + } +} ``` -Yields: +### `CompilerFunction` -```html - - - - - πŸ‘‹πŸŒ - - - -

Hello world!

- - +Regular function to compile a tree (TypeScript type). + +###### Type + +```ts +type CompilerFunction< + Tree extends Node = Node, + Result extends CompileResults = CompileResults +> = (tree: Tree, file: VFile) => Result ``` -### `processor.data([key[, value]])` +### `Data` -Configure the processor with info available to all plugins. -Information is stored in an object. +Interface of known data that can be supported by all plugins (TypeScript type). 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][overview]. +during all phases. -> πŸ‘‰ **Note**: setting information cannot occur on *[frozen][freeze]* -> processors. -> Call the processor first to create a new unfrozen processor. +To type this, do something like: -###### Signatures +```ts +declare module 'unified' { + interface Data { + htmlVoidElements?: Array | undefined + } +} -* `processor = processor.data(key, value)` -* `processor = processor.data(values)` -* `value = processor.data(key)` -* `info = processor.data()` +export {} // You may not need this, but it makes sure the file is a module. +``` -###### Parameters +###### Type -* `key` (`string`, optional) β€” identifier -* `value` (`*`, optional) β€” value to set -* `values` (`Object`, optional) β€” values to set +```ts +interface Data { + settings?: Settings | undefined +} +``` -###### Returns +See [`Settings`][api-settings] for more info. -* `processor` β€” when setting, the processor that `data` is called on -* `value` (`*`) β€” when getting, the value at `key` -* `info` (`Object`) β€” without arguments, the key-value store +### `Parser` -###### Example +A **parser** handles the parsing of text to a syntax tree (TypeScript type). -This example show how to get and set info: +It is used in the parse phase and is called with a `string` and +[`VFile`][vfile] of the document to parse. -```js -import {unified} from 'unified' +`Parser` can be a normal function, in which case it must return the syntax +tree representation of the given file ([`Node`][node]). -const processor = unified().data('alpha', 'bravo') +`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]. -processor.data('alpha') // => 'bravo' +###### Type -processor.data() // => {alpha: 'bravo'} +```ts +type Parser = ParserClass | ParserFunction +``` -processor.data({charlie: 'delta'}) +See [`ParserClass`][api-parser-class] and +[`ParserFunction`][api-parser-function] for more info. -processor.data() // => {charlie: 'delta'} -``` +### `ParserClass` -### `processor.freeze()` +Class to parse files (TypeScript type). -Freeze a processor. +###### Type -Frozen processors are meant to be extended and not to be configured directly. +```ts +type ParserClass = { + new (document: string, file: VFile): ParserClass['prototype'] + prototype: { + parse(): Tree + } +} +``` -When a processor is frozen it cannot be unfrozen. -New processors working the same way can be created by calling the processor. +### `ParserFunction` -It’s possible to freeze processors explicitly by calling `.freeze()`. -Processors freeze automatically when [`.parse()`][parse], [`.run()`][run], -[`.runSync()`][run-sync], [`.stringify()`][stringify], [`.process()`][process], -or [`.processSync()`][process-sync] are called. +Regular function to parse a file (TypeScript type). -###### Returns +###### Type -The processor that `freeze` was called on (`processor`). +```ts +type ParserFunction = (document: string, file: VFile) => Tree +``` -###### Example +### `Pluggable` -This example, `index.js`, shows how `rehype` prevents extensions to itself: +Union of the different ways to add plugins and settings (TypeScript type). -```js -import {unified} from 'unified' -import rehypeParse from 'rehype-parse' -import rehypeStringify from 'rehype-stringify' +###### Type -export const rehype = unified().use(rehypeParse).use(rehypeStringify).freeze() +```ts +type Pluggable = + | Plugin, any, any> + | PluginTuple, any, any> + | Preset ``` -That processor can be used and configured like so: +See [`Plugin`][api-plugin], [`PluginTuple`][api-plugin-tuple], +and [`Preset`][api-preset] for more info. -```js -import {rehype} from 'rehype' -import rehypeFormat from 'rehype-format' -// … +### `PluggableList` -rehype() - .use(rehypeFormat) - // … -``` +List of plugins and presets (TypeScript type). -A similar looking example is broken as operates on the frozen interface. -If this behavior was allowed it would result in unexpected behavior so an error -is thrown. -**This is not valid**: - -```js -import {rehype} from 'rehype' -import rehypeFormat from 'rehype-format' -// … +###### Type -rehype - .use(rehypeFormat) - // … +```ts +type PluggableList = Array ``` -Yields: - -```txt -~/node_modules/unified/index.js:426 - throw new Error( - ^ +See [`Pluggable`][api-pluggable] for more info. -Error: Cannot call `use` on a frozen processor. -Create a new processor first, by calling it: use `processor()` instead of `processor`. - at assertUnfrozen (~/node_modules/unified/index.js:426:11) - at Function.use (~/node_modules/unified/index.js:165:5) - … -``` +### `Plugin` -## `Plugin` +Single plugin (TypeScript type). -**Plugins** configure the processors they are applied on in the following ways: +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 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][attacher]. +In practise, 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. + +###### Type + +```ts +type Plugin< + PluginParameters extends unknown[] = [], + Input extends Node | string | undefined = Node, + Output = Input +> = ( + 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 + : // Inspect/transform. + | Transformer< + Input extends Node ? Input : Node, + Output extends Node ? Output : Node + > + | undefined + | void +``` + +See [`Transformer`][api-transformer] for more info. ###### Example @@ -1008,7 +1220,7 @@ export function move(options) { throw new Error('Missing `options.extname`') } - return function (tree, file) { + return function (_, file) { if (file.extname && file.extname !== options.extname) { file.extname = options.extname } @@ -1016,21 +1228,21 @@ export function move(options) { } ``` -`index.md`: +`example.md`: ```markdown # Hello, world! ``` -`index.js`: +`example.js`: ```js -import {read, write} from 'to-vfile' -import {reporter} from 'vfile-reporter' -import {unified} from 'unified' +import rehypeStringify from 'rehype-stringify' import remarkParse from 'remark-parse' import remarkRehype from 'remark-rehype' -import rehypeStringify from 'rehype-stringify' +import {read, write} from 'to-vfile' +import {unified} from 'unified' +import {reporter} from 'vfile-reporter' import {move} from './move.js' const file = await unified() @@ -1038,103 +1250,71 @@ const file = await unified() .use(remarkRehype) .use(move, {extname: '.html'}) .use(rehypeStringify) - .process(await read('index.md')) + .process(await read('example.md')) console.error(reporter(file)) -await write(file) // Written to `index.html`. - +await write(file) // Written to `example.html`. ``` Yields: ```txt -index.md: no issues found +example.md: no issues found ``` -…and in `index.html`: +…and in `example.html`: ```html

Hello, world!

``` -### `function attacher(options?)` - -Attachers are materialized plugins. -They are functions that can receive options and configure the processor. +### `PluginTuple` -Attachers change the processor, such as the [parser][], the [compiler][], -by configuring [data][], or by specifying how the tree and file are handled. +Tuple of a plugin and its configuration (TypeScript type). -> πŸ‘‰ **Note**: attachers are called when the processor is *[frozen][freeze]*, -> not when they are applied. +The first item is a plugin, the rest are its parameters. -###### Parameters - -* `this` (`processor`) β€” processor the attacher is applied to -* `options` (`*`, optional) β€” configuration - -###### Returns - -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, -or passed to [`next`][next]), the process stops. - -The run phase is handled by [`trough`][trough], see its documentation for the -exact semantics of these functions. - -###### Parameters - -* `tree` ([`Node`][node]) β€” tree to handle -* `file` ([`VFile`][vfile]) β€” file to handle -* `next` ([`Function`][next], optional) β€” callback - -###### Returns +###### Type -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`][node] β€” new, changed, tree +```ts +type PluginTuple< + TupleParameters extends unknown[] = [], + Input extends Node | string | undefined = undefined, + Output = undefined +> = [ + plugin: Plugin, + ...parameters: TupleParameters +] +``` -#### `function next(err[, tree[, file]])` +See [`Plugin`][api-plugin] for more info. -If the signature of a `transformer` accepts a third argument, the transformer -may perform asynchronous operations, and must call `next()`. +### `Preset` -###### Parameters +Sharable configuration (TypeScript type). -* `err` (`Error`, optional) β€” fatal error to stop the process -* `tree` ([`Node`][node], optional) β€” new, changed, tree -* `file` ([`VFile`][vfile], optional) β€” new, changed, file - -## `Preset` +They can contain plugins and settings. -Presets are sharable configuration. +###### Fields -They can contain plugins and settings. +* `plugins` ([`PluggableList`][api-pluggable-list], optional) + β€” list of plugins and presets +* `settings` ([`Data`][api-data], optional) + β€” shared settings for parsers and compilers ###### Example `preset.js`: ```js -import remarkPresetLintRecommended from 'remark-preset-lint-recommended' -import remarkPresetLintConsistent from 'remark-preset-lint-consistent' import remarkCommentConfig from 'remark-comment-config' -import remarkToc from 'remark-toc' import remarkLicense from 'remark-license' +import remarkPresetLintConsistent from 'remark-preset-lint-consistent' +import remarkPresetLintRecommended from 'remark-preset-lint-recommended' +import remarkToc from 'remark-toc' -export const preset = { - settings: {bullet: '*', emphasis: '*', fences: true}, +/** @type {import('unified').Preset} */ +const preset = { plugins: [ remarkPresetLintRecommended, remarkPresetLintConsistent, @@ -1142,7 +1322,10 @@ export const preset = { [remarkToc, {maxDepth: 3, tight: true}], remarkLicense ] + settings: {bullet: '*', emphasis: '*', fences: true}, } + +export default preset ``` `example.md`: @@ -1159,13 +1342,13 @@ _Emphasis_ and **importance**. ## License ``` -`index.js`: +`example.js`: ```js -import {read, write} from 'to-vfile' import {remark} from 'remark' +import {read, write} from 'to-vfile' import {reporter} from 'vfile-reporter' -import {preset} from './preset.js' +import preset from './preset.js' const file = await remark() .use(preset) @@ -1200,51 +1383,188 @@ example.md: no issues found [MIT](license) Β© [Titus Wormer](https://wooorm.com) ``` +### `ProcessCallback` + +Callback called when the process is done (TypeScript type). + +Called with either an error or a result. + +###### Parameters + +* `error` (`Error`, optional) + β€” fatal error +* `file` ([`VFile`][vfile], optional) + β€” processed file + +###### Returns + +Nothing (`undefined`). + +###### Example + +This example shows how `process` can be used to process a file with a callback. + +```js +import remarkGithub from 'remark-github' +import remarkParse from 'remark-parse' +import remarkStringify from 'remark-stringify' +import {unified} from 'unified' +import {reporter} from 'vfile-reporter' + +unified() + .use(remarkParse) + .use(remarkGithub) + .use(remarkStringify) + .process('@unifiedjs', function (error, file) { + if (error) throw error + if (file) { + console.error(reporter(file)) + console.log(String(file)) + } + }) +``` + +Yields: + +```txt +no issues found +``` + +```markdown +[**@unifiedjs**](https://github.com/unifiedjs) +``` + +### `Processor` + +Type of a [`processor`][api-processor] (TypeScript type). + +### `RunCallback` + +Callback called when transformers are done (TypeScript type). + +Called with either an error or results. + +###### Parameters + +* `error` (`Error`, optional) + β€” fatal error +* `tree` ([`Node`][node], optional) + β€” transformed tree +* `file` ([`VFile`][vfile], optional) + β€” file + +###### Returns + +Nothing (`undefined`). + +### `Settings` + +Interface of known extra options, that can be supported by parser and +compilers. + +This exists so that users can use packages such as `remark`, which configure +both parsers and compilers (in this case `remark-parse` and +`remark-stringify`), and still provide options for them. + +When you make parsers or compilers, that could be packaged up together, you +should support `this.data('settings')` as input and merge it with explicitly +passed `options`. +Then, to type it, using `remark-stringify` as an example, do something like: + +```ts +declare module 'unified' { + interface Settings { + bullet: '*' | '+' | '-' + // … + } +} + +export {} // You may not need this, but it makes sure the file is a module. +``` + +###### Type + +```ts +interface Settings {} +``` + +### `TransformCallback` + +Callback passed to transforms (TypeScript type). + +If the signature of a `transformer` accepts a third argument, the transformer +may perform asynchronous operations, and must call it. + +###### Parameters + +* `error` (`Error`, optional) + β€” fatal error to stop the process +* `tree` ([`Node`][node], optional) + β€” new, changed, tree +* `file` ([`VFile`][vfile], optional) + β€” new, changed, file + +###### Returns + +Nothing (`undefined`). + +### `Transformer` + +Transformers handle syntax trees and files (TypeScript type). + +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. + +###### Type + +```ts +type Transformer< + Input extends Node = Node, + Output extends Node = Input +> = ( + tree: Input, + file: VFile, + next: TransformCallback +) => + | Promise + | Output + | Error + | undefined +``` + ## Types This package is fully typed with [TypeScript][]. -It exports the following additional types: - -* `Processor` - β€” processor, where `ParseTree` is the tree that the parser creates, - `CurrentTree` the tree that the current plugin yields, `CompileTree` the - tree that the compiler accepts, and `CompileResult` the thing that the - compiler yields -* `FrozenProcessor` - β€” like `Processor` but frozen -* `Plugin` - β€” plugin, where `PluginParameters` are the accepted arguments, `Input` the - input value, and `Output` the output value (see below) -* `Pluggable` -* `Preset` - β€” preset -* `PluginTuple` - β€” plugin tuple -* `Pluggable` - β€” any usable value, where `PluginParameters` are the accepted arguments -* `PluggableList` - β€” list of plugins and presets -* `Transformer` - β€” transformer, where `Input` and `Output` are the input/output trees -* `TransformCallback` - β€” third argument of a transformer -* `Parser` - β€” parser as a class or normal function, where `Tree` is the resulting tree -* `ParserClass` - β€” parser class -* `ParserFunction` - β€” parser function -* `Compiler` - β€” compiler as a class or normal function, where `Tree` is the accepted tree - and `Result` the thing that the compiler yields -* `CompilerClass` - β€” compiler class -* `CompilerFunction` - β€” compiler function -* `RunCallback` - β€” callback to `run`, where `Tree` is the resulting tree -* `ProcessCallback` - β€” callback to `process`, where `File` is the resulting file +It exports the additional types +[`CompileResultMap`][api-compile-result-map], +[`CompileResults`][api-compile-results], +[`Compiler`][api-compiler], +[`CompilerClass`][api-compiler-class], +[`CompilerFunction`][api-compiler-function], +[`Data`][api-data], +[`Parser`][api-parser], +[`ParserClass`][api-parser-class], +[`ParserFunction`][api-parser-function], +[`Pluggable`][api-pluggable], +[`PluggableList`][api-pluggable-list], +[`Plugin`][api-plugin], +[`PluginTuple`][api-plugin-tuple], +[`Preset`][api-preset], +[`ProcessCallback`][api-process-callback], +[`Processor`][api-processor], +[`RunCallback`][api-run-callback], +[`Settings`][api-settings], +[`TransformCallback`][api-transform-callback], +and [`Transformer`][api-transformer] For TypeScript to work, it is particularly important to type your plugins correctly. @@ -1255,22 +1575,25 @@ node types for the syntax trees provided by our packages (as in, ```js /** - * @typedef {import('mdast').Root} MdastRoot * @typedef {import('hast').Root} HastRoot - * + * @typedef {import('mdast').Root} MdastRoot + */ + +/** * @typedef Options * Configuration (optional). - * @property {boolean} [someField] + * @property {boolean | null | undefined} [someField] * Some option (optional). */ // To type options: -/** @type {import('unified').Plugin<[Options?]>} */ +/** @type {import('unified').Plugin<[(Options | null | undefined)?]>} */ export function myPluginAcceptingOptions(options) { - // `options` is `Options?`. + const settings = options || {} + // `settings` is now `Options`. } -// To type a plugin that works on a certain tree: +// To type a plugin that works on a certain tree, without options: /** @type {import('unified').Plugin<[], MdastRoot>} */ export function myRemarkPlugin() { return function (tree, file) { @@ -1298,10 +1621,13 @@ export function rehypeStringify(options) {} ## Compatibility -Projects maintained by the unified collective are compatible with all maintained +Projects maintained by the unified collective are compatible with maintained versions of Node.js. -As of now, that is Node.js 12.20+, 14.14+, and 16.0+. -Our projects sometimes work with older versions, but this is not guaranteed. + +When we cut a new major release, we drop support for unmaintained versions of +Node. +This means we try to keep the current release line, `unified@^10`, compatible +with Node.js 12. ## Contribute @@ -1422,9 +1748,9 @@ work on [`ware`][ware], as it was a huge initial inspiration. [downloads]: https://www.npmjs.com/package/unified -[size-badge]: https://img.shields.io/bundlephobia/minzip/unified.svg +[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=unified -[size]: https://bundlephobia.com/result?p=unified +[size]: https://bundlejs.com/?q=unified [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg @@ -1478,10 +1804,10 @@ work on [`ware`][ware], as it was a huge initial inspiration. [nlcst]: https://github.com/syntax-tree/nlcst -[xast]: https://github.com/syntax-tree/xast - [unist]: https://github.com/syntax-tree/unist +[xast]: https://github.com/syntax-tree/xast + [unified-engine]: https://github.com/unifiedjs/unified-engine [unified-args]: https://github.com/unifiedjs/unified-args @@ -1492,98 +1818,108 @@ work on [`ware`][ware], as it was a huge initial inspiration. [unified-stream]: https://github.com/unifiedjs/unified-stream -[remark-rehype]: https://github.com/remarkjs/remark-rehype - -[remark-retext]: https://github.com/remarkjs/remark-retext +[rehype-remark]: https://github.com/rehypejs/rehype-remark [rehype-retext]: https://github.com/rehypejs/rehype-retext -[rehype-remark]: https://github.com/rehypejs/rehype-remark +[remark-rehype]: https://github.com/remarkjs/remark-rehype + +[remark-retext]: https://github.com/remarkjs/remark-retext [node]: https://github.com/syntax-tree/unist#node [vfile]: https://github.com/vfile/vfile -[vfile-value]: https://github.com/vfile/vfile#vfilevalue +[vfile-compatible]: https://github.com/vfile/vfile#compatible + +[vfile-value]: https://github.com/vfile/vfile#value [vfile-utilities]: https://github.com/vfile/vfile#list-of-utilities -[overview]: #overview +[rehype-react]: https://github.com/rehypejs/rehype-react -[file]: #file +[trough]: https://github.com/wooorm/trough#function-fninput-next -[api]: #api +[rehype-plugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins -[process]: #processorprocessfile-done +[remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins -[process-sync]: #processorprocesssyncfile +[retext-plugins]: https://github.com/retextjs/retext/blob/main/doc/plugins.md#list-of-plugins -[parse]: #processorparsefile +[awesome-rehype]: https://github.com/rehypejs/awesome-rehype -[parser]: #processorparser +[awesome-remark]: https://github.com/remarkjs/awesome-remark -[stringify]: #processorstringifytree-file +[awesome-retext]: https://github.com/retextjs/awesome-retext -[run]: #processorruntree-file-done +[topic-rehype-plugin]: https://github.com/topics/rehype-plugin -[run-sync]: #processorrunsynctree-file +[topic-remark-plugin]: https://github.com/topics/remark-plugin -[compiler]: #processorcompiler +[topic-retext-plugin]: https://github.com/topics/retext-plugin -[data]: #processordatakey-value +[types-hast]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/hast -[attacher]: #function-attacheroptions +[types-mdast]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mdast -[transformer]: #function-transformertree-file-next +[types-nlcst]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/nlcst -[next]: #function-nexterr-tree-file +[preliminary]: https://github.com/retextjs/retext/commit/8fcb1f -[freeze]: #processorfreeze +[externalised]: https://github.com/remarkjs/remark/commit/9892ec -[plugin]: #plugin +[published]: https://github.com/unifiedjs/unified/commit/2ba1cf -[run-done]: #function-doneerr-tree-file +[ware]: https://github.com/segmentio/ware -[process-done]: #function-doneerr-file +[api]: #api [contribute]: #contribute +[overview]: #overview + [sponsor]: #sponsor -[rehype-react]: https://github.com/rehypejs/rehype-react +[api-compile-result-map]: #compileresultmap -[trough]: https://github.com/wooorm/trough#function-fninput-next +[api-compile-results]: #compileresults -[promise]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise +[api-compiler]: #compiler -[remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins +[api-compiler-class]: #compilerclass -[rehype-plugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins +[api-compiler-function]: #compilerfunction -[retext-plugins]: https://github.com/retextjs/retext/blob/main/doc/plugins.md#list-of-plugins +[api-data]: #data -[awesome-remark]: https://github.com/remarkjs/awesome-remark +[api-freeze]: #processorfreeze -[awesome-rehype]: https://github.com/rehypejs/awesome-rehype +[api-parser]: #parser -[awesome-retext]: https://github.com/retextjs/awesome-retext +[api-parser-class]: #parserclass -[topic-remark-plugin]: https://github.com/topics/remark-plugin +[api-parser-function]: #parserfunction -[topic-rehype-plugin]: https://github.com/topics/rehype-plugin +[api-pluggable]: #pluggable -[topic-retext-plugin]: https://github.com/topics/retext-plugin +[api-pluggable-list]: #pluggablelist -[types-hast]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/hast +[api-plugin]: #plugin -[types-mdast]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mdast +[api-plugin-tuple]: #plugintuple -[types-nlcst]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/nlcst +[api-preset]: #preset -[preliminary]: https://github.com/retextjs/retext/commit/8fcb1f#diff-168726dbe96b3ce427e7fedce31bb0bc +[api-process]: #processorprocessfile-done -[externalised]: https://github.com/remarkjs/remark/commit/9892ec#diff-168726dbe96b3ce427e7fedce31bb0bc +[api-process-callback]: #processcallback -[published]: https://github.com/unifiedjs/unified/commit/2ba1cf +[api-processor]: #processor -[ware]: https://github.com/segmentio/ware +[api-run-callback]: #runcallback + +[api-settings]: #settings + +[api-transform-callback]: #transformcallback + +[api-transformer]: #transformer diff --git a/test/types.d.ts b/test/types.d.ts index 5c293242..b83ae37d 100644 --- a/test/types.d.ts +++ b/test/types.d.ts @@ -1,7 +1,10 @@ declare module 'unified' { interface Data { + alpha?: boolean | undefined + bar?: boolean | undefined baz?: 'qux' | undefined - foo?: 'bar' | undefined + foo?: 'bar' | boolean | undefined + qux?: boolean | undefined x?: boolean | undefined } }