diff --git a/index.test-d.ts b/index.test-d.ts index 2523938e..442c3295 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -162,7 +162,6 @@ unified().use(pluginWithSeveralArgumentsImplicit, {example: ''}, 1) unified() .use(pluginWithOptions, {example: ''}) - .use([pluginWithOptions, {example: ''}]) .use([[pluginWithOptions, {example: ''}]]) .use({ plugins: [[pluginWithOptions, {example: ''}]] @@ -173,8 +172,6 @@ unified() unified() .use(pluginWithoutOptions, true) .use(pluginWithoutOptions, false) - .use([pluginWithoutOptions, true]) - .use([pluginWithoutOptions, false]) .use([ [pluginWithoutOptions, true], [pluginWithoutOptions, false] diff --git a/lib/callable-instance.js b/lib/callable-instance.js index 6c092d88..e42eb011 100644 --- a/lib/callable-instance.js +++ b/lib/callable-instance.js @@ -1,4 +1,3 @@ -/* eslint-disable unicorn/no-this-assignment */ /** * @param {string} property */ diff --git a/lib/index.js b/lib/index.js index f81bc3bd..f51374db 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,3 @@ -/* eslint-disable unicorn/no-this-assignment */ /** * @typedef {import('trough').Pipeline} Pipeline * @@ -20,9 +19,9 @@ /** * @template {Node} [Tree=Node] - * The node that the compiler receives. + * The node that the compiler receives (default: `Node`). * @template {CompileResults} [Result=CompileResults] - * The thing that the compiler yields. + * The thing that the compiler yields (default: `CompileResults`). * @typedef {CompilerClass | CompilerFunction} Compiler * A **compiler** handles the compiling of a syntax tree to something else * (in most cases, text). @@ -54,7 +53,9 @@ /** * @template {Node} [Tree=Node] + * The node that the compiler receives (default: `Node`) * @template {CompileResults} [Result=CompileResults] + * The thing that the compiler yields (default: `CompileResults`). * @typedef {({ * prototype: {compile(): Result} * new (tree: Tree, file: VFile): CompilerClass['prototype'] @@ -64,9 +65,9 @@ /** * @template {Node} [Tree=Node] - * The node that the compiler receives. + * The node that the compiler receives (default: `Node`). * @template {CompileResults} [Result=CompileResults] - * The thing that the compiler yields. + * The thing that the compiler yields (default: `CompileResults`). * @callback CompilerFunction * Regular function to compile a tree. * @param {Tree} tree @@ -80,7 +81,7 @@ /** * @template {Node} [Tree=Node] - * The node that the parser yields. + * The node that the parser yields (default: `Node`) * @typedef {ParserClass | ParserFunction} Parser * A **parser** handles the parsing of text to a syntax tree. * @@ -98,6 +99,7 @@ /** * @template {Node} [Tree=Node] + * The node that the parser yields (default: `Node`). * @typedef {({ * prototype: {parse(): Tree} * new (document: string, file: VFile): ParserClass['prototype'] @@ -107,7 +109,7 @@ /** * @template {Node} [Tree=Node] - * The node that the parser yields. + * The node that the parser yields (default: `Node`). * @callback ParserFunction * Regular function to parse a file. * @param {string} document @@ -136,9 +138,9 @@ // . /** * @template {Array} [PluginParameters=[]] - * Arguments passed to the plugin. + * Arguments passed to the plugin (default: `[]`, the empty tuple). * @template {Node | string | undefined} [Input=Node] - * Value that is expected as input. + * Value that is expected as input (default: `Node`). * * * If the plugin returns a {@link Transformer `Transformer`}, this * should be the node it expects. @@ -147,7 +149,7 @@ * * If the plugin sets a {@link Compiler `Compiler`}, this should be the * node it expects. * @template [Output=Input] - * Value that is yielded as output. + * Value that is yielded as output (default: `Input`). * * * If the plugin returns a {@link Transformer `Transformer`}, this * should be the node that that yields. @@ -191,9 +193,9 @@ * The first item is a plugin, the rest are its parameters. * * @template {Array} [TupleParameters=[]] - * Arguments passed to the plugin. + * Arguments passed to the plugin (default: `[]`, the empty tuple). * @template {Node | string | undefined} [Input=undefined] - * Value that is expected as input. + * Value that is expected as input (optional). * * * If the plugin returns a {@link Transformer `Transformer`}, this * should be the node it expects. @@ -201,7 +203,7 @@ * `string`. * * If the plugin sets a {@link Compiler `Compiler`}, this should be the * node it expects. - * @template [Output=undefined] + * @template [Output=undefined] (optional). * @typedef {( * [ * plugin: Plugin, @@ -224,48 +226,48 @@ * * They can contain plugins and settings. * @property {PluggableList | undefined} [plugins] - * List of plugins and presets. + * List of plugins and presets (optional). * @property {Record | undefined} [settings] - * Shared settings for parsers and compilers. + * Shared settings for parsers and compilers (optional). */ /** * @template {VFile} [File=VFile] - * The file that the callback receives. + * 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. + * Fatal error (optional). * @param {File | undefined} [file] - * Processed file. + * Processed file (optional). * @returns {undefined} * Nothing. */ /** * @template {Node} [Tree=Node] - * The tree that the callback receives. + * 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. + * Fatal error (optional). * @param {Tree | undefined} [tree] - * Transformed tree. + * Transformed tree (optional). * @param {VFile | undefined} [file] - * File. + * File (optional). * @returns {undefined} * Nothing. */ /** * @template {Node} [Input=Node] - * Node type that the transformer expects. + * Node type that the transformer expects (default: `Node`). * @template {Node} [Output=Input] - * Node type that the transformer yields. + * Node type that the transformer yields (default: `Input`). * @callback Transformer * Transformers handle syntax trees and files. * @@ -303,7 +305,7 @@ /** * @template {Node} [Output=Node] - * Node type that the transformer yields. + * 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()`. @@ -331,7 +333,7 @@ * @template {Node | string | undefined} Input * Input of plugin. * @template Output - * Output of plugin. + * Output of plugin (optional). * @typedef {( * Input extends string * ? Output extends Node | undefined @@ -391,6 +393,7 @@ import structuredClone from '@ungap/structured-clone' import {bail} from 'bail' +import {ok as assert} from 'devlop' import isPlainObj from 'is-plain-obj' import {trough} from 'trough' import {VFile} from 'vfile' @@ -400,15 +403,15 @@ const own = {}.hasOwnProperty /** * @template {Node | undefined} [ParseTree=undefined] - * Output of `parse`. + * Output of `parse` (optional). * @template {Node | undefined} [HeadTree=undefined] - * Input for `run`. + * Input for `run` (optional). * @template {Node | undefined} [TailTree=undefined] - * Output for `run`. + * Output for `run` (optional). * @template {Node | undefined} [CompileTree=undefined] - * Input of `stringify`. + * Input of `stringify` (optional). * @template {CompileResults | undefined} [CompileResult=undefined] - * Output of `stringify`. + * Output of `stringify` (optional). * @extends {CallableInstance<[], Processor>} */ export class Processor extends CallableInstance { @@ -588,9 +591,9 @@ export class Processor extends CallableInstance { * * @param {Record | string} [key] * Key to get or set, or entire dataset to set, or nothing to get the - * entire dataset. + * entire dataset (optional). * @param {unknown} [value] - * Value to set. + * 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. @@ -679,8 +682,8 @@ export class Processor extends CallableInstance { * > phases. * * @param {VFileCompatible | undefined} [file] - * file to parse; typically `string`; any value accepted as `x` in - * `new VFile(x)`. + * file to parse (optional); typically `string`; any value accepted as `x` + * in `new VFile(x)`. * @returns {ParseTree extends undefined ? Node : ParseTree} * Syntax tree representing `file`. */ @@ -726,9 +729,9 @@ export class Processor extends CallableInstance { * @returns {Promise>} * * @param {VFileCompatible | undefined} [file] - * File; any value accepted as `x` in `new VFile(x)`. + * File (optional); any value accepted as `x` in `new VFile(x)`. * @param {ProcessCallback> | undefined} [done] - * Callback. + * Callback (optional). * @returns {Promise | undefined} * Nothing if `done` is given. * Otherwise `Promise`, rejected with a fatal error or resolved with the @@ -752,20 +755,17 @@ export class Processor extends CallableInstance { */ process(file, done) { const self = this + this.freeze() assertParser('process', this.Parser) assertCompiler('process', this.Compiler) - if (!done) { - return new Promise(executor) - } - - executor(undefined, done) + return done ? executor(undefined, done) : new Promise(executor) // Note: `void`s needed for TS. /** - * @param {((file: VFile) => undefined | void) | undefined} resolve - * @param {(error?: Error | undefined) => undefined | void} reject + * @param {((file: VFileWithOutput) => undefined | void) | undefined} resolve + * @param {(error: Error | undefined) => undefined | void} reject * @returns {undefined} */ function executor(resolve, reject) { @@ -779,32 +779,32 @@ export class Processor extends CallableInstance { self.run(parseTree, realFile, function (error, tree, file) { if (error || !tree || !file) { - realDone(error) + 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 (compileResult === null || compileResult === undefined) { + // Empty. + } else if (looksLikeAVFileValue(compileResult)) { + file.value = compileResult } else { - // 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 result = self.stringify(compileTree, file) - - if (result === null || result === undefined) { - // Empty. - } else if (looksLikeAVFileValue(result)) { - file.value = result - } else { - file.result = result - } - - realDone(error, file) + file.result = compileResult } + + realDone(error, /** @type {VFileWithOutput} */ (file)) }) /** - * @param {Error | undefined} [error] - * @param {VFile | undefined} [file] + * @param {Error | undefined} error + * @param {VFileWithOutput | undefined} [file] * @returns {undefined} */ function realDone(error, file) { @@ -813,7 +813,7 @@ export class Processor extends CallableInstance { } else if (resolve) { resolve(file) } else { - // @ts-expect-error: `done` is defined if `resolve` is not. + assert(done, '`done` is defined if `resolve` is not') done(undefined, file) } } @@ -830,7 +830,7 @@ export class Processor extends CallableInstance { * > 👉 **Note**: `processSync` performs the parse, run, and stringify phases. * * @param {VFileCompatible | undefined} [file] - * File; any value accepted as `x` in `new VFile(x)`. + * File (optional); any value accepted as `x` in `new VFile(x)`. * @returns {VFileWithOutput} * The processed file. * @@ -851,29 +851,28 @@ export class Processor extends CallableInstance { * [rehype-react]: https://github.com/rehypejs/rehype-react */ processSync(file) { - /** @type {boolean | undefined} */ - let complete + /** @type {boolean} */ + let complete = false + /** @type {VFileWithOutput | undefined} */ + let result this.freeze() assertParser('processSync', this.Parser) assertCompiler('processSync', this.Compiler) - // The result will be set by `this.process` on this file. - const realFile = /** @type {VFileWithOutput} */ (vfile(file)) - - this.process(realFile, realDone) - + this.process(file, realDone) assertDone('processSync', 'process', complete) + assert(result, 'we either bailed on an error or have a tree') - return realFile + return result /** - * @param {Error | undefined} [error] - * @returns {undefined} + * @type {ProcessCallback>} */ - function realDone(error) { + function realDone(error, file) { complete = true bail(error) + result = file } } @@ -909,6 +908,7 @@ export class Processor extends CallableInstance { * File associated with `node` (optional); any value accepted as `x` in * `new VFile(x)`. * @param {RunCallback} [done] + * Callback (optional). * @returns {Promise | undefined} * Nothing if `done` is given. * Otherwise, `Promise` rejected with a fatal error or resolved with the @@ -925,11 +925,7 @@ export class Processor extends CallableInstance { file = undefined } - if (!done) { - return new Promise(executor) - } - - executor(undefined, done) + return done ? executor(undefined, done) : new Promise(executor) // Note: `void`s needed for TS. /** @@ -941,7 +937,10 @@ export class Processor extends CallableInstance { * @returns {undefined} */ function executor(resolve, reject) { - // @ts-expect-error: `file` can’t be a `done` anymore, we checked. + assert( + typeof file !== 'function', + '`file` can’t be a `done` anymore, we checked' + ) const realFile = vfile(file) transformers.run(tree, realFile, realDone) @@ -962,7 +961,7 @@ export class Processor extends CallableInstance { } else if (resolve) { resolve(resultingTree) } else { - // @ts-expect-error: `done` is defined if `resolve` is not. + assert(done, '`done` is defined if `resolve` is not') done(undefined, resultingTree, file) } } @@ -987,22 +986,19 @@ export class Processor extends CallableInstance { * Transformed tree. */ runSync(tree, file) { - /** @type {Node | undefined} */ + /** @type {boolean} */ + let complete = false + /** @type {(TailTree extends undefined ? Node : TailTree) | undefined} */ let result - /** @type {boolean | undefined} */ - let complete this.run(tree, file, realDone) assertDone('runSync', 'run', complete) - - // @ts-expect-error: we either bailed on an error or have a tree. + assert(result, 'we either bailed on an error or have a tree') return result /** - * @param {Error | undefined} [error] - * @param {Node} [tree] - * @returns {undefined} + * @type {RunCallback} */ function realDone(error, tree) { bail(error) @@ -1118,18 +1114,14 @@ export class Processor extends CallableInstance { * @param {...(Parameters | [boolean])} parameters * @returns {UsePlugin} * - * @overload - * @param {[plugin: Plugin, enable: boolean] | [plugin: Plugin, ...parameters: Parameters]} tuple - * @returns {UsePlugin} - * - * @param {[plugin: Plugin, ...parameters: Array] | PluggableList | Plugin | Preset | null | undefined} value + * @param {PluggableList | Plugin | Preset | null | undefined} value * Usable value. - * @param {...unknown} options + * @param {...unknown} parameters * Parameters, when a plugin is given as a usable value. * @returns {Processor} * Current processor. */ - use(value, ...options) { + use(value, ...parameters) { const attachers = this.attachers const namespace = this.namespace /** @type {Record | undefined} */ @@ -1140,10 +1132,9 @@ export class Processor extends CallableInstance { if (value === null || value === undefined) { // Empty. } else if (typeof value === 'function') { - addPlugin(value, ...options) + addPlugin(value, parameters) } else if (typeof value === 'object') { if (Array.isArray(value)) { - // @ts-expect-error: look at tuples? addList(value) } else { addPreset(value) @@ -1165,12 +1156,12 @@ export class Processor extends CallableInstance { */ function add(value) { if (typeof value === 'function') { - addPlugin(value) + addPlugin(value, []) } else if (typeof value === 'object') { if (Array.isArray(value)) { - const [plugin, ...options] = - /** @type {[Plugin, ...Array]} */ (value) - addPlugin(plugin, ...options) + const [plugin, ...parameters] = + /** @type {PluginTuple>} */ (value) + addPlugin(plugin, parameters) } else { addPreset(value) } @@ -1199,7 +1190,7 @@ export class Processor extends CallableInstance { } /** - * @param {PluggableList | null | undefined} [plugins] + * @param {PluggableList | null | undefined} plugins * @returns {undefined} */ function addList(plugins) { @@ -1219,10 +1210,10 @@ export class Processor extends CallableInstance { /** * @param {Plugin} plugin - * @param {...unknown} [value] + * @param {Array} parameters * @returns {undefined} */ - function addPlugin(plugin, value) { + function addPlugin(plugin, parameters) { let index = -1 /** @type {PluginTuple> | undefined} */ let entry @@ -1235,14 +1226,18 @@ export class Processor extends CallableInstance { } if (entry) { + let value = parameters[0] if (isPlainObj(entry[1]) && isPlainObj(value)) { value = structuredClone({...entry[1], ...value}) } entry[1] = value + // To do: should the rest be set? } else { - // @ts-expect-error: fine. - attachers.push([...arguments]) + // It’s important to pass arguments, because an explicit passed + // `undefined` is different from a not-passed `value`. + // This way we keep things at their place. + attachers.push([plugin, ...parameters]) } } } @@ -1256,29 +1251,27 @@ export const unified = new Processor().freeze() /** * Check if `value` is a constructor. * - * @template {unknown} Value - * @param {Value} value + * @param {unknown} value * @param {string} name * @returns {boolean} */ function newable(value, name) { - return ( - typeof value === 'function' && - // Prototypes do exist. - // type-coverage:ignore-next-line - value.prototype && - // A function with keys in its prototype is probably a constructor. - // Classes’ prototype methods are not enumerable, so we check if some value - // exists in the prototype. + const proto = + // Prototypes are `unknown`. // type-coverage:ignore-next-line - (keys(value.prototype) || name in value.prototype) + typeof value === 'function' && /** @type {unknown} */ (value.prototype) + + return ( + proto !== null && + typeof proto === 'object' && + (keys(proto) || name in proto) ) } /** * Check if `value` is an object with keys. * - * @param {Record} value + * @param {object} value * @returns {boolean} */ function keys(value) { @@ -1369,7 +1362,7 @@ function assertDone(name, asyncName, complete) { } /** - * @param {VFileCompatible} [value] + * @param {VFileCompatible | undefined} [value] * @returns {VFile} */ function vfile(value) { @@ -1377,7 +1370,7 @@ function vfile(value) { } /** - * @param {VFileCompatible} [value] + * @param {VFileCompatible | undefined} [value] * @returns {value is VFile} */ function looksLikeAVFile(value) { diff --git a/package.json b/package.json index a51d02db..235a4bb8 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "bail": "^2.0.0", + "devlop": "^1.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" @@ -110,6 +111,9 @@ } } ], - "prettier": true + "prettier": true, + "rules": { + "unicorn/no-this-assignment": "off" + } } } diff --git a/readme.md b/readme.md index 6ac7a007..ce24b8ae 100644 --- a/readme.md +++ b/readme.md @@ -1261,7 +1261,7 @@ node types for the syntax trees provided by our packages (as in, * @typedef Options * Configuration (optional). * @property {boolean} [someField] - * Some option. + * Some option (optional). */ // To type options: diff --git a/test/process-compilers.js b/test/process-compilers.js index 88c89d56..716fbbdf 100644 --- a/test/process-compilers.js +++ b/test/process-compilers.js @@ -52,15 +52,15 @@ test('process (compilers)', async function (t) { const processor = unified() const result = { _owner: null, - type: 'p', - ref: null, key: 'h-1', - props: {children: ['bravo']} + props: {children: ['bravo']}, + ref: null, + type: 'p' } processor.Parser = simpleParser - // @ts-expect-error: custom node, which should be registered!. + // @ts-expect-error: custom result, which should be registered! processor.Compiler = function () { return result }