site/node_modules/mdast-util-to-markdown/lib/index.js

194 lines
5 KiB
JavaScript
Raw Normal View History

2024-10-14 06:09:33 +00:00
/**
* @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)
}