astro-ghostcms/.pnpm-store/v3/files/b1/c4f35196c389d68d8a84e69258f...

600 lines
14 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
/**
* @typedef {import('unist').Node} Node
* @typedef {import('vfile').VFileCompatible} VFileCompatible
* @typedef {import('vfile').VFileValue} VFileValue
* @typedef {import('..').Processor} Processor
* @typedef {import('..').Plugin} Plugin
* @typedef {import('..').Preset} Preset
* @typedef {import('..').Pluggable} Pluggable
* @typedef {import('..').PluggableList} PluggableList
* @typedef {import('..').Transformer} Transformer
* @typedef {import('..').Parser} Parser
* @typedef {import('..').Compiler} Compiler
* @typedef {import('..').RunCallback} RunCallback
* @typedef {import('..').ProcessCallback} ProcessCallback
*
* @typedef Context
* @property {Node} tree
* @property {VFile} file
*/
import {bail} from 'bail'
import isBuffer from 'is-buffer'
import extend from 'extend'
import isPlainObj from 'is-plain-obj'
import {trough} from 'trough'
import {VFile} from 'vfile'
// Expose a frozen processor.
export const unified = base().freeze()
const own = {}.hasOwnProperty
// Function to create the first processor.
/**
* @returns {Processor}
*/
function base() {
const transformers = trough()
/** @type {Processor['attachers']} */
const attachers = []
/** @type {Record<string, unknown>} */
let namespace = {}
/** @type {boolean|undefined} */
let frozen
let freezeIndex = -1
// Data management.
// @ts-expect-error: overloads are handled.
processor.data = data
processor.Parser = undefined
processor.Compiler = undefined
// Lock.
processor.freeze = freeze
// Plugins.
processor.attachers = attachers
// @ts-expect-error: overloads are handled.
processor.use = use
// API.
processor.parse = parse
processor.stringify = stringify
// @ts-expect-error: overloads are handled.
processor.run = run
processor.runSync = runSync
// @ts-expect-error: overloads are handled.
processor.process = process
processor.processSync = processSync
// Expose.
return processor
// Create a new processor based on the processor in the current scope.
/** @type {Processor} */
function processor() {
const destination = base()
let index = -1
while (++index < attachers.length) {
destination.use(...attachers[index])
}
destination.data(extend(true, {}, namespace))
return destination
}
/**
* @param {string|Record<string, unknown>} [key]
* @param {unknown} [value]
* @returns {unknown}
*/
function data(key, value) {
if (typeof key === 'string') {
// Set `key`.
if (arguments.length === 2) {
assertUnfrozen('data', frozen)
namespace[key] = value
return processor
}
// Get `key`.
return (own.call(namespace, key) && namespace[key]) || null
}
// Set space.
if (key) {
assertUnfrozen('data', frozen)
namespace = key
return processor
}
// Get space.
return namespace
}
/** @type {Processor['freeze']} */
function freeze() {
if (frozen) {
return processor
}
while (++freezeIndex < attachers.length) {
const [attacher, ...options] = attachers[freezeIndex]
if (options[0] === false) {
continue
}
if (options[0] === true) {
options[0] = undefined
}
/** @type {Transformer|void} */
const transformer = attacher.call(processor, ...options)
if (typeof transformer === 'function') {
transformers.use(transformer)
}
}
frozen = true
freezeIndex = Number.POSITIVE_INFINITY
return processor
}
/**
* @param {Pluggable|null|undefined} [value]
* @param {...unknown} options
* @returns {Processor}
*/
function use(value, ...options) {
/** @type {Record<string, unknown>|undefined} */
let settings
assertUnfrozen('use', frozen)
if (value === null || value === undefined) {
// Empty.
} else if (typeof value === 'function') {
addPlugin(value, ...options)
} else if (typeof value === 'object') {
if (Array.isArray(value)) {
addList(value)
} else {
addPreset(value)
}
} else {
throw new TypeError('Expected usable value, not `' + value + '`')
}
if (settings) {
namespace.settings = Object.assign(namespace.settings || {}, settings)
}
return processor
/**
* @param {import('..').Pluggable<unknown[]>} value
* @returns {void}
*/
function add(value) {
if (typeof value === 'function') {
addPlugin(value)
} else if (typeof value === 'object') {
if (Array.isArray(value)) {
const [plugin, ...options] = value
addPlugin(plugin, ...options)
} else {
addPreset(value)
}
} else {
throw new TypeError('Expected usable value, not `' + value + '`')
}
}
/**
* @param {Preset} result
* @returns {void}
*/
function addPreset(result) {
addList(result.plugins)
if (result.settings) {
settings = Object.assign(settings || {}, result.settings)
}
}
/**
* @param {PluggableList|null|undefined} [plugins]
* @returns {void}
*/
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 {...unknown} [value]
* @returns {void}
*/
function addPlugin(plugin, value) {
let index = -1
/** @type {Processor['attachers'][number]|undefined} */
let entry
while (++index < attachers.length) {
if (attachers[index][0] === plugin) {
entry = attachers[index]
break
}
}
if (entry) {
if (isPlainObj(entry[1]) && isPlainObj(value)) {
value = extend(true, entry[1], value)
}
entry[1] = value
} else {
// @ts-expect-error: fine.
attachers.push([...arguments])
}
}
}
/** @type {Processor['parse']} */
function parse(doc) {
processor.freeze()
const file = vfile(doc)
const Parser = processor.Parser
assertParser('parse', Parser)
if (newable(Parser, 'parse')) {
// @ts-expect-error: `newable` checks this.
return new Parser(String(file), file).parse()
}
// @ts-expect-error: `newable` checks this.
return Parser(String(file), file) // eslint-disable-line new-cap
}
/** @type {Processor['stringify']} */
function stringify(node, doc) {
processor.freeze()
const file = vfile(doc)
const Compiler = processor.Compiler
assertCompiler('stringify', Compiler)
assertNode(node)
if (newable(Compiler, 'compile')) {
// @ts-expect-error: `newable` checks this.
return new Compiler(node, file).compile()
}
// @ts-expect-error: `newable` checks this.
return Compiler(node, file) // eslint-disable-line new-cap
}
/**
* @param {Node} node
* @param {VFileCompatible|RunCallback} [doc]
* @param {RunCallback} [callback]
* @returns {Promise<Node>|void}
*/
function run(node, doc, callback) {
assertNode(node)
processor.freeze()
if (!callback && typeof doc === 'function') {
callback = doc
doc = undefined
}
if (!callback) {
return new Promise(executor)
}
executor(null, callback)
/**
* @param {null|((node: Node) => void)} resolve
* @param {(error: Error) => void} reject
* @returns {void}
*/
function executor(resolve, reject) {
// @ts-expect-error: `doc` cant be a callback anymore, we checked.
transformers.run(node, vfile(doc), done)
/**
* @param {Error|null} error
* @param {Node} tree
* @param {VFile} file
* @returns {void}
*/
function done(error, tree, file) {
tree = tree || node
if (error) {
reject(error)
} else if (resolve) {
resolve(tree)
} else {
// @ts-expect-error: `callback` is defined if `resolve` is not.
callback(null, tree, file)
}
}
}
}
/** @type {Processor['runSync']} */
function runSync(node, file) {
/** @type {Node|undefined} */
let result
/** @type {boolean|undefined} */
let complete
processor.run(node, file, done)
assertDone('runSync', 'run', complete)
// @ts-expect-error: we either bailed on an error or have a tree.
return result
/**
* @param {Error|null} [error]
* @param {Node} [tree]
* @returns {void}
*/
function done(error, tree) {
bail(error)
result = tree
complete = true
}
}
/**
* @param {VFileCompatible} doc
* @param {ProcessCallback} [callback]
* @returns {Promise<VFile>|undefined}
*/
function process(doc, callback) {
processor.freeze()
assertParser('process', processor.Parser)
assertCompiler('process', processor.Compiler)
if (!callback) {
return new Promise(executor)
}
executor(null, callback)
/**
* @param {null|((file: VFile) => void)} resolve
* @param {(error?: Error|null|undefined) => void} reject
* @returns {void}
*/
function executor(resolve, reject) {
const file = vfile(doc)
processor.run(processor.parse(file), file, (error, tree, file) => {
if (error || !tree || !file) {
done(error)
} else {
/** @type {unknown} */
const result = processor.stringify(tree, file)
if (result === undefined || result === null) {
// Empty.
} else if (looksLikeAVFileValue(result)) {
file.value = result
} else {
file.result = result
}
done(error, file)
}
})
/**
* @param {Error|null|undefined} [error]
* @param {VFile|undefined} [file]
* @returns {void}
*/
function done(error, file) {
if (error || !file) {
reject(error)
} else if (resolve) {
resolve(file)
} else {
// @ts-expect-error: `callback` is defined if `resolve` is not.
callback(null, file)
}
}
}
}
/** @type {Processor['processSync']} */
function processSync(doc) {
/** @type {boolean|undefined} */
let complete
processor.freeze()
assertParser('processSync', processor.Parser)
assertCompiler('processSync', processor.Compiler)
const file = vfile(doc)
processor.process(file, done)
assertDone('processSync', 'process', complete)
return file
/**
* @param {Error|null|undefined} [error]
* @returns {void}
*/
function done(error) {
complete = true
bail(error)
}
}
}
/**
* Check if `value` is a constructor.
*
* @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.
// type-coverage:ignore-next-line
(keys(value.prototype) || name in value.prototype)
)
}
/**
* Check if `value` is an object with keys.
*
* @param {Record<string, unknown>} value
* @returns {boolean}
*/
function keys(value) {
/** @type {string} */
let key
for (key in value) {
if (own.call(value, key)) {
return true
}
}
return false
}
/**
* 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 {VFileCompatible} [value]
* @returns {VFile}
*/
function vfile(value) {
return looksLikeAVFile(value) ? value : new VFile(value)
}
/**
* @param {VFileCompatible} [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 VFileValue}
*/
function looksLikeAVFileValue(value) {
return typeof value === 'string' || isBuffer(value)
}