/** * @typedef {import('micromark-util-types').Encoding} Encoding * @typedef {import('micromark-util-types').Event} Event * @typedef {import('micromark-util-types').ParseOptions} ParseOptions * @typedef {import('micromark-util-types').Token} Token * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext * @typedef {import('micromark-util-types').Value} Value * @typedef {import('unist').Parent} UnistParent * @typedef {import('unist').Point} Point * @typedef {import('mdast').PhrasingContent} PhrasingContent * @typedef {import('mdast').Content} Content * @typedef {Root|Content} Node * @typedef {Extract} Parent * @typedef {import('mdast').Break} Break * @typedef {import('mdast').Blockquote} Blockquote * @typedef {import('mdast').Code} Code * @typedef {import('mdast').Definition} Definition * @typedef {import('mdast').Emphasis} Emphasis * @typedef {import('mdast').Heading} Heading * @typedef {import('mdast').HTML} HTML * @typedef {import('mdast').Image} Image * @typedef {import('mdast').ImageReference} ImageReference * @typedef {import('mdast').InlineCode} InlineCode * @typedef {import('mdast').Link} Link * @typedef {import('mdast').LinkReference} LinkReference * @typedef {import('mdast').List} List * @typedef {import('mdast').ListItem} ListItem * @typedef {import('mdast').Paragraph} Paragraph * @typedef {import('mdast').Root} Root * @typedef {import('mdast').Strong} Strong * @typedef {import('mdast').Text} Text * @typedef {import('mdast').ThematicBreak} ThematicBreak * * @typedef {UnistParent & {type: 'fragment', children: Array}} Fragment */ /** * @typedef _CompileDataFields * @property {boolean|undefined} expectingFirstListItemValue * @property {boolean|undefined} flowCodeInside * @property {boolean|undefined} setextHeadingSlurpLineEnding * @property {boolean|undefined} atHardBreak * @property {'collapsed'|'full'} referenceType * @property {boolean|undefined} inReference * @property {'characterReferenceMarkerHexadecimal'|'characterReferenceMarkerNumeric'} characterReferenceType * * @typedef {Record & Partial<_CompileDataFields>} CompileData * * @typedef {(tree: Root) => Root|void} Transform * @typedef {(this: CompileContext, token: Token) => void} Handle * @typedef {Record} Handles * Token types mapping to handles * @typedef {Record|Array> & {canContainEols: Array, transforms: Array, enter: Handles, exit: Handles}} NormalizedExtension * @typedef {Partial} Extension * An mdast extension changes how markdown tokens are turned into mdast. * * @typedef {(this: Omit, left: Token|undefined, right: Token) => void} OnEnterError * @typedef {(this: Omit, left: Token, right: Token) => void} OnExitError * * @typedef CompileContext * mdast compiler context * @property {Array} stack * @property {Array<[Token, OnEnterError|undefined]>} tokenStack * @property {(key: string, value?: unknown) => void} setData * Set data into the key-value store. * @property {(key: K) => CompileData[K]} getData * Get data from the key-value store. * @property {(this: CompileContext) => void} buffer * Capture some of the output data. * @property {(this: CompileContext) => string} resume * Stop capturing and access the output data. * @property {(this: CompileContext, node: N, token: Token, onError?: OnEnterError) => N} enter * Enter a token. * @property {(this: CompileContext, token: Token, onError?: OnExitError) => Node} exit * Exit a token. * @property {TokenizeContext['sliceSerialize']} sliceSerialize * Get the string value of a token. * @property {NormalizedExtension} config * Configuration. * * @typedef {{mdastExtensions?: Array>}} FromMarkdownOptions * @typedef {ParseOptions & FromMarkdownOptions} Options */ import {ok as assert} from 'uvu/assert' import {toString} from 'mdast-util-to-string' import {parse} from 'micromark/lib/parse.js' import {preprocess} from 'micromark/lib/preprocess.js' import {postprocess} from 'micromark/lib/postprocess.js' import {decodeNumericCharacterReference} from 'micromark-util-decode-numeric-character-reference' import {decodeString} from 'micromark-util-decode-string' import {normalizeIdentifier} from 'micromark-util-normalize-identifier' import {codes} from 'micromark-util-symbol/codes.js' import {constants} from 'micromark-util-symbol/constants.js' import {types} from 'micromark-util-symbol/types.js' import {decodeNamedCharacterReference} from 'decode-named-character-reference' import {stringifyPosition} from 'unist-util-stringify-position' const own = {}.hasOwnProperty /** * @param value Markdown to parse (`string` or `Buffer`). * @param [encoding] Character encoding to understand `value` as when it’s a `Buffer` (`string`, default: `'utf8'`). * @param [options] Configuration */ export const fromMarkdown = /** * @type {( * ((value: Value, encoding: Encoding, options?: Options) => Root) & * ((value: Value, options?: Options) => Root) * )} */ ( /** * @param {Value} value * @param {Encoding} [encoding] * @param {Options} [options] * @returns {Root} */ function (value, encoding, options) { if (typeof encoding !== 'string') { options = encoding encoding = undefined } return compiler(options)( postprocess( parse(options).document().write(preprocess()(value, encoding, true)) ) ) } ) /** * Note this compiler only understand complete buffering, not streaming. * * @param {Options} [options] */ function compiler(options = {}) { /** @type {NormalizedExtension} */ // @ts-expect-error: our base has all required fields, so the result will too. const config = configure( { transforms: [], canContainEols: [ 'emphasis', 'fragment', 'heading', 'paragraph', 'strong' ], enter: { autolink: opener(link), autolinkProtocol: onenterdata, autolinkEmail: onenterdata, atxHeading: opener(heading), blockQuote: opener(blockQuote), characterEscape: onenterdata, characterReference: onenterdata, codeFenced: opener(codeFlow), codeFencedFenceInfo: buffer, codeFencedFenceMeta: buffer, codeIndented: opener(codeFlow, buffer), codeText: opener(codeText, buffer), codeTextData: onenterdata, data: onenterdata, codeFlowValue: onenterdata, definition: opener(definition), definitionDestinationString: buffer, definitionLabelString: buffer, definitionTitleString: buffer, emphasis: opener(emphasis), hardBreakEscape: opener(hardBreak), hardBreakTrailing: opener(hardBreak), htmlFlow: opener(html, buffer), htmlFlowData: onenterdata, htmlText: opener(html, buffer), htmlTextData: onenterdata, image: opener(image), label: buffer, link: opener(link), listItem: opener(listItem), listItemValue: onenterlistitemvalue, listOrdered: opener(list, onenterlistordered), listUnordered: opener(list), paragraph: opener(paragraph), reference: onenterreference, referenceString: buffer, resourceDestinationString: buffer, resourceTitleString: buffer, setextHeading: opener(heading), strong: opener(strong), thematicBreak: opener(thematicBreak) }, exit: { atxHeading: closer(), atxHeadingSequence: onexitatxheadingsequence, autolink: closer(), autolinkEmail: onexitautolinkemail, autolinkProtocol: onexitautolinkprotocol, blockQuote: closer(), characterEscapeValue: onexitdata, characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker, characterReferenceMarkerNumeric: onexitcharacterreferencemarker, characterReferenceValue: onexitcharacterreferencevalue, codeFenced: closer(onexitcodefenced), codeFencedFence: onexitcodefencedfence, codeFencedFenceInfo: onexitcodefencedfenceinfo, codeFencedFenceMeta: onexitcodefencedfencemeta, codeFlowValue: onexitdata, codeIndented: closer(onexitcodeindented), codeText: closer(onexitcodetext), codeTextData: onexitdata, data: onexitdata, definition: closer(), definitionDestinationString: onexitdefinitiondestinationstring, definitionLabelString: onexitdefinitionlabelstring, definitionTitleString: onexitdefinitiontitlestring, emphasis: closer(), hardBreakEscape: closer(onexithardbreak), hardBreakTrailing: closer(onexithardbreak), htmlFlow: closer(onexithtmlflow), htmlFlowData: onexitdata, htmlText: closer(onexithtmltext), htmlTextData: onexitdata, image: closer(onexitimage), label: onexitlabel, labelText: onexitlabeltext, lineEnding: onexitlineending, link: closer(onexitlink), listItem: closer(), listOrdered: closer(), listUnordered: closer(), paragraph: closer(), referenceString: onexitreferencestring, resourceDestinationString: onexitresourcedestinationstring, resourceTitleString: onexitresourcetitlestring, resource: onexitresource, setextHeading: closer(onexitsetextheading), setextHeadingLineSequence: onexitsetextheadinglinesequence, setextHeadingText: onexitsetextheadingtext, strong: closer(), thematicBreak: closer() } }, options.mdastExtensions || [] ) /** @type {CompileData} */ const data = {} return compile /** * @param {Array} events * @returns {Root} */ function compile(events) { /** @type {Root} */ let tree = {type: 'root', children: []} /** @type {CompileContext['stack']} */ const stack = [tree] /** @type {CompileContext['tokenStack']} */ const tokenStack = [] /** @type {Array} */ const listStack = [] /** @type {Omit} */ const context = { stack, tokenStack, config, enter, exit, buffer, resume, setData, getData } let index = -1 while (++index < events.length) { // We preprocess lists to add `listItem` tokens, and to infer whether // items the list itself are spread out. if ( events[index][1].type === types.listOrdered || events[index][1].type === types.listUnordered ) { if (events[index][0] === 'enter') { listStack.push(index) } else { const tail = listStack.pop() assert(typeof tail === 'number', 'expected list ot be open') index = prepareList(events, tail, index) } } } index = -1 while (++index < events.length) { const handler = config[events[index][0]] if (own.call(handler, events[index][1].type)) { handler[events[index][1].type].call( Object.assign( {sliceSerialize: events[index][2].sliceSerialize}, context ), events[index][1] ) } } if (tokenStack.length > 0) { const tail = tokenStack[tokenStack.length - 1] const handler = tail[1] || defaultOnError handler.call(context, undefined, tail[0]) } // Figure out `root` position. tree.position = { start: point( events.length > 0 ? events[0][1].start : {line: 1, column: 1, offset: 0} ), end: point( events.length > 0 ? events[events.length - 2][1].end : {line: 1, column: 1, offset: 0} ) } index = -1 while (++index < config.transforms.length) { tree = config.transforms[index](tree) || tree } return tree } /** * @param {Array} events * @param {number} start * @param {number} length * @returns {number} */ function prepareList(events, start, length) { let index = start - 1 let containerBalance = -1 let listSpread = false /** @type {Token|undefined} */ let listItem /** @type {number|undefined} */ let lineIndex /** @type {number|undefined} */ let firstBlankLineIndex /** @type {boolean|undefined} */ let atMarker while (++index <= length) { const event = events[index] if ( event[1].type === types.listUnordered || event[1].type === types.listOrdered || event[1].type === types.blockQuote ) { if (event[0] === 'enter') { containerBalance++ } else { containerBalance-- } atMarker = undefined } else if (event[1].type === types.lineEndingBlank) { if (event[0] === 'enter') { if ( listItem && !atMarker && !containerBalance && !firstBlankLineIndex ) { firstBlankLineIndex = index } atMarker = undefined } } else if ( event[1].type === types.linePrefix || event[1].type === types.listItemValue || event[1].type === types.listItemMarker || event[1].type === types.listItemPrefix || event[1].type === types.listItemPrefixWhitespace ) { // Empty. } else { atMarker = undefined } if ( (!containerBalance && event[0] === 'enter' && event[1].type === types.listItemPrefix) || (containerBalance === -1 && event[0] === 'exit' && (event[1].type === types.listUnordered || event[1].type === types.listOrdered)) ) { if (listItem) { let tailIndex = index lineIndex = undefined while (tailIndex--) { const tailEvent = events[tailIndex] if ( tailEvent[1].type === types.lineEnding || tailEvent[1].type === types.lineEndingBlank ) { if (tailEvent[0] === 'exit') continue if (lineIndex) { events[lineIndex][1].type = types.lineEndingBlank listSpread = true } tailEvent[1].type = types.lineEnding lineIndex = tailIndex } else if ( tailEvent[1].type === types.linePrefix || tailEvent[1].type === types.blockQuotePrefix || tailEvent[1].type === types.blockQuotePrefixWhitespace || tailEvent[1].type === types.blockQuoteMarker || tailEvent[1].type === types.listItemIndent ) { // Empty } else { break } } if ( firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex) ) { // @ts-expect-error Patched. listItem._spread = true } // Fix position. listItem.end = Object.assign( {}, lineIndex ? events[lineIndex][1].start : event[1].end ) events.splice(lineIndex || index, 0, ['exit', listItem, event[2]]) index++ length++ } // Create a new list item. if (event[1].type === types.listItemPrefix) { listItem = { type: 'listItem', // @ts-expect-error Patched _spread: false, start: Object.assign({}, event[1].start) } // @ts-expect-error: `listItem` is most definitely defined, TS... events.splice(index, 0, ['enter', listItem, event[2]]) index++ length++ firstBlankLineIndex = undefined atMarker = true } } } // @ts-expect-error Patched. events[start][1]._spread = listSpread return length } /** * @type {CompileContext['setData']} * @param [value] */ function setData(key, value) { data[key] = value } /** * @type {CompileContext['getData']} * @template {string} K * @param {K} key * @returns {CompileData[K]} */ function getData(key) { return data[key] } /** * @param {Point} d * @returns {Point} */ function point(d) { return {line: d.line, column: d.column, offset: d.offset} } /** * @param {(token: Token) => Node} create * @param {Handle} [and] * @returns {Handle} */ function opener(create, and) { return open /** * @this {CompileContext} * @param {Token} token * @returns {void} */ function open(token) { enter.call(this, create(token), token) if (and) and.call(this, token) } } /** @type {CompileContext['buffer']} */ function buffer() { this.stack.push({type: 'fragment', children: []}) } /** * @type {CompileContext['enter']} * @template {Node} N * @this {CompileContext} * @param {N} node * @param {Token} token * @param {OnEnterError} [errorHandler] * @returns {N} */ function enter(node, token, errorHandler) { const parent = this.stack[this.stack.length - 1] assert(parent, 'expected `parent`') assert('children' in parent, 'expected `parent`') // @ts-expect-error: Assume `Node` can exist as a child of `parent`. parent.children.push(node) this.stack.push(node) this.tokenStack.push([token, errorHandler]) // @ts-expect-error: `end` will be patched later. node.position = {start: point(token.start)} return node } /** * @param {Handle} [and] * @returns {Handle} */ function closer(and) { return close /** * @this {CompileContext} * @param {Token} token * @returns {void} */ function close(token) { if (and) and.call(this, token) exit.call(this, token) } } /** * @type {CompileContext['exit']} * @this {CompileContext} * @param {Token} token * @param {OnExitError} [onExitError] * @returns {Node} */ function exit(token, onExitError) { const node = this.stack.pop() assert(node, 'expected `node`') const open = this.tokenStack.pop() if (!open) { throw new Error( 'Cannot close `' + token.type + '` (' + stringifyPosition({start: token.start, end: token.end}) + '): it’s not open' ) } else if (open[0].type !== token.type) { if (onExitError) { onExitError.call(this, token, open[0]) } else { const handler = open[1] || defaultOnError handler.call(this, token, open[0]) } } assert(node.type !== 'fragment', 'unexpected fragment `exit`ed') assert(node.position, 'expected `position` to be defined') node.position.end = point(token.end) return node } /** * @this {CompileContext} * @returns {string} */ function resume() { return toString(this.stack.pop()) } // // Handlers. // /** @type {Handle} */ function onenterlistordered() { setData('expectingFirstListItemValue', true) } /** @type {Handle} */ function onenterlistitemvalue(token) { if (getData('expectingFirstListItemValue')) { const ancestor = /** @type {List} */ (this.stack[this.stack.length - 2]) ancestor.start = Number.parseInt( this.sliceSerialize(token), constants.numericBaseDecimal ) setData('expectingFirstListItemValue') } } /** @type {Handle} */ function onexitcodefencedfenceinfo() { const data = this.resume() const node = /** @type {Code} */ (this.stack[this.stack.length - 1]) node.lang = data } /** @type {Handle} */ function onexitcodefencedfencemeta() { const data = this.resume() const node = /** @type {Code} */ (this.stack[this.stack.length - 1]) node.meta = data } /** @type {Handle} */ function onexitcodefencedfence() { // Exit if this is the closing fence. if (getData('flowCodeInside')) return this.buffer() setData('flowCodeInside', true) } /** @type {Handle} */ function onexitcodefenced() { const data = this.resume() const node = /** @type {Code} */ (this.stack[this.stack.length - 1]) node.value = data.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, '') setData('flowCodeInside') } /** @type {Handle} */ function onexitcodeindented() { const data = this.resume() const node = /** @type {Code} */ (this.stack[this.stack.length - 1]) node.value = data.replace(/(\r?\n|\r)$/g, '') } /** @type {Handle} */ function onexitdefinitionlabelstring(token) { // Discard label, use the source content instead. const label = this.resume() const node = /** @type {Definition} */ (this.stack[this.stack.length - 1]) node.label = label node.identifier = normalizeIdentifier( this.sliceSerialize(token) ).toLowerCase() } /** @type {Handle} */ function onexitdefinitiontitlestring() { const data = this.resume() const node = /** @type {Definition} */ (this.stack[this.stack.length - 1]) node.title = data } /** @type {Handle} */ function onexitdefinitiondestinationstring() { const data = this.resume() const node = /** @type {Definition} */ (this.stack[this.stack.length - 1]) node.url = data } /** @type {Handle} */ function onexitatxheadingsequence(token) { const node = /** @type {Heading} */ (this.stack[this.stack.length - 1]) if (!node.depth) { const depth = this.sliceSerialize(token).length assert( depth === 1 || depth === 2 || depth === 3 || depth === 4 || depth === 5 || depth === 6, 'expected `depth` between `1` and `6`' ) node.depth = depth } } /** @type {Handle} */ function onexitsetextheadingtext() { setData('setextHeadingSlurpLineEnding', true) } /** @type {Handle} */ function onexitsetextheadinglinesequence(token) { const node = /** @type {Heading} */ (this.stack[this.stack.length - 1]) node.depth = this.sliceSerialize(token).charCodeAt(0) === codes.equalsTo ? 1 : 2 } /** @type {Handle} */ function onexitsetextheading() { setData('setextHeadingSlurpLineEnding') } /** @type {Handle} */ function onenterdata(token) { const parent = /** @type {Parent} */ (this.stack[this.stack.length - 1]) /** @type {Node} */ let tail = parent.children[parent.children.length - 1] if (!tail || tail.type !== 'text') { // Add a new text node. tail = text() // @ts-expect-error: we’ll add `end` later. tail.position = {start: point(token.start)} // @ts-expect-error: Assume `parent` accepts `text`. parent.children.push(tail) } this.stack.push(tail) } /** @type {Handle} */ function onexitdata(token) { const tail = this.stack.pop() assert(tail, 'expected a `node` to be on the stack') assert('value' in tail, 'expected a `literal` to be on the stack') assert(tail.position, 'expected `node` to have an open position') tail.value += this.sliceSerialize(token) tail.position.end = point(token.end) } /** @type {Handle} */ function onexitlineending(token) { const context = this.stack[this.stack.length - 1] assert(context, 'expected `node`') // If we’re at a hard break, include the line ending in there. if (getData('atHardBreak')) { assert('children' in context, 'expected `parent`') const tail = context.children[context.children.length - 1] assert(tail.position, 'expected tail to have a starting position') tail.position.end = point(token.end) setData('atHardBreak') return } if ( !getData('setextHeadingSlurpLineEnding') && config.canContainEols.includes(context.type) ) { onenterdata.call(this, token) onexitdata.call(this, token) } } /** @type {Handle} */ function onexithardbreak() { setData('atHardBreak', true) } /** @type {Handle} */ function onexithtmlflow() { const data = this.resume() const node = /** @type {HTML} */ (this.stack[this.stack.length - 1]) node.value = data } /** @type {Handle} */ function onexithtmltext() { const data = this.resume() const node = /** @type {HTML} */ (this.stack[this.stack.length - 1]) node.value = data } /** @type {Handle} */ function onexitcodetext() { const data = this.resume() const node = /** @type {InlineCode} */ (this.stack[this.stack.length - 1]) node.value = data } /** @type {Handle} */ function onexitlink() { const context = /** @type {Link & {identifier: string, label: string}} */ ( this.stack[this.stack.length - 1] ) // To do: clean. if (getData('inReference')) { context.type += 'Reference' // @ts-expect-error: mutate. context.referenceType = getData('referenceType') || 'shortcut' // @ts-expect-error: mutate. delete context.url delete context.title } else { // @ts-expect-error: mutate. delete context.identifier // @ts-expect-error: mutate. delete context.label } setData('referenceType') } /** @type {Handle} */ function onexitimage() { const context = /** @type {Image & {identifier: string, label: string}} */ ( this.stack[this.stack.length - 1] ) // To do: clean. if (getData('inReference')) { context.type += 'Reference' // @ts-expect-error: mutate. context.referenceType = getData('referenceType') || 'shortcut' // @ts-expect-error: mutate. delete context.url delete context.title } else { // @ts-expect-error: mutate. delete context.identifier // @ts-expect-error: mutate. delete context.label } setData('referenceType') } /** @type {Handle} */ function onexitlabeltext(token) { const ancestor = /** @type {(Link|Image) & {identifier: string, label: string}} */ ( this.stack[this.stack.length - 2] ) const string = this.sliceSerialize(token) ancestor.label = decodeString(string) ancestor.identifier = normalizeIdentifier(string).toLowerCase() } /** @type {Handle} */ function onexitlabel() { const fragment = /** @type {Fragment} */ (this.stack[this.stack.length - 1]) const value = this.resume() const node = /** @type {(Link|Image) & {identifier: string, label: string}} */ ( this.stack[this.stack.length - 1] ) // Assume a reference. setData('inReference', true) if (node.type === 'link') { // @ts-expect-error: Assume static phrasing content. node.children = fragment.children } else { node.alt = value } } /** @type {Handle} */ function onexitresourcedestinationstring() { const data = this.resume() const node = /** @type {Link|Image} */ (this.stack[this.stack.length - 1]) node.url = data } /** @type {Handle} */ function onexitresourcetitlestring() { const data = this.resume() const node = /** @type {Link|Image} */ (this.stack[this.stack.length - 1]) node.title = data } /** @type {Handle} */ function onexitresource() { setData('inReference') } /** @type {Handle} */ function onenterreference() { setData('referenceType', 'collapsed') } /** @type {Handle} */ function onexitreferencestring(token) { const label = this.resume() const node = /** @type {LinkReference|ImageReference} */ ( this.stack[this.stack.length - 1] ) node.label = label node.identifier = normalizeIdentifier( this.sliceSerialize(token) ).toLowerCase() setData('referenceType', 'full') } /** @type {Handle} */ function onexitcharacterreferencemarker(token) { setData('characterReferenceType', token.type) } /** @type {Handle} */ function onexitcharacterreferencevalue(token) { const data = this.sliceSerialize(token) const type = getData('characterReferenceType') /** @type {string} */ let value if (type) { value = decodeNumericCharacterReference( data, type === types.characterReferenceMarkerNumeric ? constants.numericBaseDecimal : constants.numericBaseHexadecimal ) setData('characterReferenceType') } else { // @ts-expect-error `decodeNamedCharacterReference` can return false for // invalid named character references, but everything we’ve tokenized is // valid. value = decodeNamedCharacterReference(data) } const tail = this.stack.pop() assert(tail, 'expected `node`') assert(tail.position, 'expected `node.position`') assert('value' in tail, 'expected `node.value`') tail.value += value tail.position.end = point(token.end) } /** @type {Handle} */ function onexitautolinkprotocol(token) { onexitdata.call(this, token) const node = /** @type {Link} */ (this.stack[this.stack.length - 1]) node.url = this.sliceSerialize(token) } /** @type {Handle} */ function onexitautolinkemail(token) { onexitdata.call(this, token) const node = /** @type {Link} */ (this.stack[this.stack.length - 1]) node.url = 'mailto:' + this.sliceSerialize(token) } // // Creaters. // /** @returns {Blockquote} */ function blockQuote() { return {type: 'blockquote', children: []} } /** @returns {Code} */ function codeFlow() { return {type: 'code', lang: null, meta: null, value: ''} } /** @returns {InlineCode} */ function codeText() { return {type: 'inlineCode', value: ''} } /** @returns {Definition} */ function definition() { return { type: 'definition', identifier: '', label: null, title: null, url: '' } } /** @returns {Emphasis} */ function emphasis() { return {type: 'emphasis', children: []} } /** @returns {Heading} */ function heading() { // @ts-expect-error `depth` will be set later. return {type: 'heading', depth: undefined, children: []} } /** @returns {Break} */ function hardBreak() { return {type: 'break'} } /** @returns {HTML} */ function html() { return {type: 'html', value: ''} } /** @returns {Image} */ function image() { return {type: 'image', title: null, url: '', alt: null} } /** @returns {Link} */ function link() { return {type: 'link', title: null, url: '', children: []} } /** * @param {Token} token * @returns {List} */ function list(token) { return { type: 'list', ordered: token.type === 'listOrdered', start: null, // @ts-expect-error Patched. spread: token._spread, children: [] } } /** * @param {Token} token * @returns {ListItem} */ function listItem(token) { return { type: 'listItem', // @ts-expect-error Patched. spread: token._spread, checked: null, children: [] } } /** @returns {Paragraph} */ function paragraph() { return {type: 'paragraph', children: []} } /** @returns {Strong} */ function strong() { return {type: 'strong', children: []} } /** @returns {Text} */ function text() { return {type: 'text', value: ''} } /** @returns {ThematicBreak} */ function thematicBreak() { return {type: 'thematicBreak'} } } /** * @param {Extension} combined * @param {Array>} extensions * @returns {Extension} */ function configure(combined, extensions) { let index = -1 while (++index < extensions.length) { const value = extensions[index] if (Array.isArray(value)) { configure(combined, value) } else { extension(combined, value) } } return combined } /** * @param {Extension} combined * @param {Extension} extension * @returns {void} */ function extension(combined, extension) { /** @type {string} */ let key for (key in extension) { if (own.call(extension, key)) { const list = key === 'canContainEols' || key === 'transforms' const maybe = own.call(combined, key) ? combined[key] : undefined /* c8 ignore next */ const left = maybe || (combined[key] = list ? [] : {}) const right = extension[key] if (right) { if (list) { // @ts-expect-error: `left` is an array. combined[key] = [...left, ...right] } else { Object.assign(left, right) } } } } } /** @type {OnEnterError} */ function defaultOnError(left, right) { if (left) { throw new Error( 'Cannot close `' + left.type + '` (' + stringifyPosition({start: left.start, end: left.end}) + '): a different token (`' + right.type + '`, ' + stringifyPosition({start: right.start, end: right.end}) + ') is open' ) } else { throw new Error( 'Cannot close document, a token (`' + right.type + '`, ' + stringifyPosition({start: right.start, end: right.end}) + ') is still open' ) } }