hast-util-excerpt
Version:
hast utility to excerpt the tree to a comment
107 lines (93 loc) • 2.97 kB
JavaScript
/**
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').RootContent} RootContent
*/
/**
* @typedef Options
* Configuration.
* @property {string | null | undefined} [comment='more']
* Comment value to search for (default: `'more'`).
* @property {Array<RootContent> | null | undefined} [ignore=[]]
* Nodes to exclude from the resulting tree (default: `[]`).
*
* These are not counted towards `size`.
* @property {number | null | undefined} [maxSearchSize=2048]
* How far to search for the comment before bailing (default: `2048`).
*
* The goal of this project is to find user-defined explicit excerpts, that
* are assumed to be somewhat reasonably placed.
* This option prevents searching giant documents for some comment that
* probably won’t be found at the end.
*/
import {truncate} from 'hast-util-truncate'
/** @type {Readonly<Options>} */
const emptyOptions = {}
/**
* Truncate `tree` to a certain comment.
*
* @template {Nodes} Tree
* Tree kind.
* @param {Tree} tree
* Tree to truncate.
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns {Tree | undefined}
* Truncated clone of `tree` when a comment is found, `undefined` otherwise.
*/
export function excerpt(tree, options) {
const config = options || emptyOptions
const comment = config.comment || 'more'
const maxSearchSize =
typeof config.maxSearchSize === 'number' ? config.maxSearchSize : 2048
let found = false
// Note: `truncate` returns a deep clone.
const result = preorder(
truncate(tree, {ignore: config.ignore, size: maxSearchSize})
)
return found ? result : undefined
/**
* Truncate `node`.
*
* @template {Nodes} Kind
* Node kind.
* @param {Kind} node
* Node to truncate.
* @returns {Kind | undefined}
* Copy of `node` or `undefined` when done.
*/
function preorder(node) {
/** @typedef {Kind extends import('unist').Parent ? Kind['children'][number] : never} Child */
if (node.type === 'comment' && node.value.trim() === comment) {
found = true
return
}
// Support MDX.
if (
(node.type === 'mdxFlowExpression' ||
node.type === 'mdxTextExpression') &&
node.data &&
node.data.estree &&
node.data.estree.comments &&
node.data.estree.comments.some(function (node) {
return node.value.trim() === comment
})
) {
found = true
return
}
/** @type {Kind} */
const replacement = {...node}
if ('children' in replacement) {
/** @type {Array<Child>} */
const children = []
let index = -1
while (++index < replacement.children.length && !found) {
const child = /** @type {Child} */ (replacement.children[index])
const result = preorder(child)
if (result) children.push(result)
}
replacement.children = children
}
return replacement
}
}