/** * @typedef {import('mdast').Nodes} Nodes * @typedef {import('./types.js').Enter} Enter * @typedef {import('./types.js').Info} Info * @typedef {import('./types.js').Join} Join * @typedef {import('./types.js').FlowParents} FlowParents * @typedef {import('./types.js').Options} Options * @typedef {import('./types.js').PhrasingParents} PhrasingParents * @typedef {import('./types.js').SafeConfig} SafeConfig * @typedef {import('./types.js').State} State * @typedef {import('./types.js').TrackFields} TrackFields */ import {zwitch} from 'zwitch' import {configure} from './configure.js' import {handle as handlers} from './handle/index.js' import {join} from './join.js' import {unsafe} from './unsafe.js' import {association} from './util/association.js' import {compilePattern} from './util/compile-pattern.js' import {containerPhrasing} from './util/container-phrasing.js' import {containerFlow} from './util/container-flow.js' import {indentLines} from './util/indent-lines.js' import {safe} from './util/safe.js' import {track} from './util/track.js' /** * Turn an mdast syntax tree into markdown. * * @param {Nodes} tree * Tree to serialize. * @param {Options} [options] * Configuration (optional). * @returns {string} * Serialized markdown representing `tree`. */ export function toMarkdown(tree, options = {}) { /** @type {State} */ const state = { enter, indentLines, associationId: association, containerPhrasing: containerPhrasingBound, containerFlow: containerFlowBound, createTracker: track, compilePattern, safe: safeBound, stack: [], unsafe: [...unsafe], join: [...join], // @ts-expect-error: GFM / frontmatter are typed in `mdast` but not defined // here. handlers: {...handlers}, options: {}, indexStack: [], // @ts-expect-error: add `handle` in a second. handle: undefined } configure(state, options) if (state.options.tightDefinitions) { state.join.push(joinDefinition) } state.handle = zwitch('type', { invalid, unknown, handlers: state.handlers }) let result = state.handle(tree, undefined, state, { before: '\n', after: '\n', now: {line: 1, column: 1}, lineShift: 0 }) if ( result && result.charCodeAt(result.length - 1) !== 10 && result.charCodeAt(result.length - 1) !== 13 ) { result += '\n' } return result /** @type {Enter} */ function enter(name) { state.stack.push(name) return exit /** * @returns {undefined} */ function exit() { state.stack.pop() } } } /** * @param {unknown} value * @returns {never} */ function invalid(value) { throw new Error('Cannot handle value `' + value + '`, expected node') } /** * @param {unknown} value * @returns {never} */ function unknown(value) { // Always a node. const node = /** @type {Nodes} */ (value) throw new Error('Cannot handle unknown node `' + node.type + '`') } /** @type {Join} */ function joinDefinition(left, right) { // No blank line between adjacent definitions. if (left.type === 'definition' && left.type === right.type) { return 0 } } /** * Serialize the children of a parent that contains phrasing children. * * These children will be joined flush together. * * @this {State} * Info passed around about the current state. * @param {PhrasingParents} parent * Parent of flow nodes. * @param {Info} info * Info on where we are in the document we are generating. * @returns {string} * Serialized children, joined together. */ function containerPhrasingBound(parent, info) { return containerPhrasing(parent, this, info) } /** * Serialize the children of a parent that contains flow children. * * These children will typically be joined by blank lines. * What they are joined by exactly is defined by `Join` functions. * * @this {State} * Info passed around about the current state. * @param {FlowParents} parent * Parent of flow nodes. * @param {TrackFields} info * Info on where we are in the document we are generating. * @returns {string} * Serialized children, joined by (blank) lines. */ function containerFlowBound(parent, info) { return containerFlow(parent, this, info) } /** * Make a string safe for embedding in markdown constructs. * * In markdown, almost all punctuation characters can, in certain cases, * result in something. * Whether they do is highly subjective to where they happen and in what * they happen. * * To solve this, `mdast-util-to-markdown` tracks: * * * Characters before and after something; * * What “constructs” we are in. * * This information is then used by this function to escape or encode * special characters. * * @this {State} * Info passed around about the current state. * @param {string | null | undefined} value * Raw value to make safe. * @param {SafeConfig} config * Configuration. * @returns {string} * Serialized markdown safe for embedding. */ function safeBound(value, config) { return safe(this, value, config) }