123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323 |
- /**
- * @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<Array<any>, any, any> |
- * PluginTuple<Array<any>, any, any> |
- * Preset
- * )} Pluggable
- * Union of the different ways to add plugins and settings.
- */
- /**
- * @typedef {Array<Pluggable>} PluggableList
- * List of plugins and presets.
- */
- // Note: we can’t use `callback` yet as it messes up `this`:
- // <https://github.com/microsoft/TypeScript/issues/55197>.
- /**
- * @template {Array<unknown>} [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<unknown>} [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<TupleParameters, Input, Output>,
- * ...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<Output>} next
- * Callback.
- * @returns {(
- * Promise<Output | undefined | void> |
- * Promise<never> | // 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<undefined>` or `undefined` — the next transformer keeps using
- * same tree
- * * `Promise<Node>` 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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>
- * : Output extends CompileResults
- * ? Input extends Node | undefined
- * ? // Compiler.
- * Processor<
- * ParseTree,
- * HeadTree,
- * TailTree,
- * Input extends undefined ? CompileTree : Input,
- * Output extends undefined ? CompileResult : Output
- * >
- * : // Unknown.
- * Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>
- * : 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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>
- * : // Unknown.
- * Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>
- * )} 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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>>}
- */
- 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<ParseTree extends undefined ? Node : ParseTree> |
- * 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<PluginTuple<Array<unknown>>>}
- */
- 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<ParseTree extends undefined ? Node : ParseTree> |
- * 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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- * 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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>} */ (
- 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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- *
- * @overload
- * @param {Key} key
- * @returns {Data[Key]}
- *
- * @overload
- * @param {Key} key
- * @param {Data[Key]} value
- * @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- *
- * @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<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- * 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<VFileWithOutput<CompileResult>>} done
- * @returns {undefined}
- *
- * @overload
- * @param {Compatible | undefined} [file]
- * @returns {Promise<VFileWithOutput<CompileResult>>}
- *
- * @param {Compatible | undefined} [file]
- * File (optional); typically `string` or `VFile`]; any value accepted as
- * `x` in `new VFile(x)`.
- * @param {ProcessCallback<VFileWithOutput<CompileResult>> | undefined} [done]
- * Callback (optional).
- * @returns {Promise<VFile> | 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<CompileResult>) => 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<CompileResult>} */ (file))
- })
- /**
- * @param {Error | undefined} error
- * @param {VFileWithOutput<CompileResult> | 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<CompileResult>}
- * 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<CompileResult> | 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<VFileWithOutput<CompileResult>>}
- */
- 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<TailTree extends undefined ? Node : TailTree>} done
- * @returns {undefined}
- *
- * @overload
- * @param {HeadTree extends undefined ? Node : HeadTree} tree
- * @param {Compatible | undefined} file
- * @param {RunCallback<TailTree extends undefined ? Node : TailTree>} done
- * @returns {undefined}
- *
- * @overload
- * @param {HeadTree extends undefined ? Node : HeadTree} tree
- * @param {Compatible | undefined} [file]
- * @returns {Promise<TailTree extends undefined ? Node : TailTree>}
- *
- * @param {HeadTree extends undefined ? Node : HeadTree} tree
- * Tree to transform and inspect.
- * @param {(
- * RunCallback<TailTree extends undefined ? Node : TailTree> |
- * Compatible
- * )} [file]
- * File associated with `node` (optional); any value accepted as `x` in
- * `new VFile(x)`.
- * @param {RunCallback<TailTree extends undefined ? Node : TailTree>} [done]
- * Callback (optional).
- * @returns {Promise<TailTree extends undefined ? Node : TailTree> | 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<TailTree extends undefined ? Node : TailTree>}
- */
- 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<unknown>} [Parameters=[]]
- * @template {Node | string | undefined} [Input=undefined]
- * @template [Output=Input]
- *
- * @overload
- * @param {Preset | null | undefined} [preset]
- * @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- *
- * @overload
- * @param {PluggableList} list
- * @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- *
- * @overload
- * @param {Plugin<Parameters, Input, Output>} plugin
- * @param {...(Parameters | [boolean])} parameters
- * @returns {UsePlugin<ParseTree, HeadTree, TailTree, CompileTree, CompileResult, Input, Output>}
- *
- * @param {PluggableList | Plugin | Preset | null | undefined} value
- * Usable value.
- * @param {...unknown} parameters
- * Parameters, when a plugin is given as a usable value.
- * @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
- * 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<Array<unknown>>} */ (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<unknown>} 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
- )
- }
|