/** * @typedef {import('unist').Node} Node * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('./complex-types').Action} Action * @typedef {import('./complex-types').Index} Index * @typedef {import('./complex-types').ActionTuple} ActionTuple * @typedef {import('./complex-types').VisitorResult} VisitorResult * @typedef {import('./complex-types').Visitor} Visitor */ import {convert} from 'unist-util-is' import {color} from './color.js' /** * Continue traversing as normal */ export const CONTINUE = true /** * Do not traverse this node’s children */ export const SKIP = 'skip' /** * Stop traversing immediately */ export const EXIT = false /** * Visit children of tree which pass a test * * @param tree Abstract syntax tree to walk * @param test Test node, optional * @param visitor Function to run for each node * @param reverse Visit the tree in reverse order, defaults to false */ export const visitParents = /** * @type {( * ((tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) & * ((tree: Tree, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test * @param {import('./complex-types').Visitor} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { if (typeof test === 'function' && typeof visitor !== 'function') { reverse = visitor // @ts-expect-error no visitor given, so `visitor` is test. visitor = test test = null } const is = convert(test) const step = reverse ? -1 : 1 factory(tree, null, [])() /** * @param {Node} node * @param {number?} index * @param {Array.} parents */ function factory(node, index, parents) { /** @type {Object.} */ // @ts-expect-error: hush const value = typeof node === 'object' && node !== null ? node : {} /** @type {string|undefined} */ let name if (typeof value.type === 'string') { name = typeof value.tagName === 'string' ? value.tagName : typeof value.name === 'string' ? value.name : undefined Object.defineProperty(visit, 'name', { value: 'node (' + color(value.type + (name ? '<' + name + '>' : '')) + ')' }) } return visit function visit() { /** @type {ActionTuple} */ let result = [] /** @type {ActionTuple} */ let subresult /** @type {number} */ let offset /** @type {Array.} */ let grandparents if (!test || is(node, index, parents[parents.length - 1] || null)) { result = toResult(visitor(node, parents)) if (result[0] === EXIT) { return result } } // @ts-expect-error looks like a parent. if (node.children && result[0] !== SKIP) { // @ts-expect-error looks like a parent. offset = (reverse ? node.children.length : -1) + step // @ts-expect-error looks like a parent. grandparents = parents.concat(node) // @ts-expect-error looks like a parent. while (offset > -1 && offset < node.children.length) { // @ts-expect-error looks like a parent. subresult = factory(node.children[offset], offset, grandparents)() if (subresult[0] === EXIT) { return subresult } offset = typeof subresult[1] === 'number' ? subresult[1] : offset + step } } return result } } } ) /** * @param {VisitorResult} value * @returns {ActionTuple} */ function toResult(value) { if (Array.isArray(value)) { return value } if (typeof value === 'number') { return [CONTINUE, value] } return [value] }