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

600 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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)
}