12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352 |
- /**
- * @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').InlineCode} InlineCode
- * @typedef {import('mdast').Link} Link
- * @typedef {import('mdast').List} List
- * @typedef {import('mdast').ListItem} ListItem
- * @typedef {import('mdast').Nodes} Nodes
- * @typedef {import('mdast').Paragraph} Paragraph
- * @typedef {import('mdast').Parent} Parent
- * @typedef {import('mdast').PhrasingContent} PhrasingContent
- * @typedef {import('mdast').ReferenceType} ReferenceType
- * @typedef {import('mdast').Root} Root
- * @typedef {import('mdast').Strong} Strong
- * @typedef {import('mdast').Text} Text
- * @typedef {import('mdast').ThematicBreak} ThematicBreak
- *
- * @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').Point} Point
- *
- * @typedef {import('../index.js').CompileData} CompileData
- */
- /**
- * @typedef {Omit<Parent, 'children' | 'type'> & {type: 'fragment', children: Array<PhrasingContent>}} Fragment
- */
- /**
- * @callback Transform
- * Extra transform, to change the AST afterwards.
- * @param {Root} tree
- * Tree to transform.
- * @returns {Root | null | undefined | void}
- * New tree or nothing (in which case the current tree is used).
- *
- * @callback Handle
- * Handle a token.
- * @param {CompileContext} this
- * Context.
- * @param {Token} token
- * Current token.
- * @returns {undefined | void}
- * Nothing.
- *
- * @typedef {Record<string, Handle>} Handles
- * Token types mapping to handles
- *
- * @callback OnEnterError
- * Handle the case where the `right` token is open, but it is closed (by the
- * `left` token) or because we reached the end of the document.
- * @param {Omit<CompileContext, 'sliceSerialize'>} this
- * Context.
- * @param {Token | undefined} left
- * Left token.
- * @param {Token} right
- * Right token.
- * @returns {undefined}
- * Nothing.
- *
- * @callback OnExitError
- * Handle the case where the `right` token is open but it is closed by
- * exiting the `left` token.
- * @param {Omit<CompileContext, 'sliceSerialize'>} this
- * Context.
- * @param {Token} left
- * Left token.
- * @param {Token} right
- * Right token.
- * @returns {undefined}
- * Nothing.
- *
- * @typedef {[Token, OnEnterError | undefined]} TokenTuple
- * Open token on the stack, with an optional error handler for when
- * that token isn’t closed properly.
- */
- /**
- * @typedef Config
- * Configuration.
- *
- * We have our defaults, but extensions will add more.
- * @property {Array<string>} canContainEols
- * Token types where line endings are used.
- * @property {Handles} enter
- * Opening handles.
- * @property {Handles} exit
- * Closing handles.
- * @property {Array<Transform>} transforms
- * Tree transforms.
- *
- * @typedef {Partial<Config>} Extension
- * Change how markdown tokens from micromark are turned into mdast.
- *
- * @typedef CompileContext
- * mdast compiler context.
- * @property {Array<Fragment | Nodes>} stack
- * Stack of nodes.
- * @property {Array<TokenTuple>} tokenStack
- * Stack of tokens.
- * @property {(this: CompileContext) => undefined} buffer
- * Capture some of the output data.
- * @property {(this: CompileContext) => string} resume
- * Stop capturing and access the output data.
- * @property {(this: CompileContext, node: Nodes, token: Token, onError?: OnEnterError) => undefined} enter
- * Enter a node.
- * @property {(this: CompileContext, token: Token, onError?: OnExitError) => undefined} exit
- * Exit a node.
- * @property {TokenizeContext['sliceSerialize']} sliceSerialize
- * Get the string value of a token.
- * @property {Config} config
- * Configuration.
- * @property {CompileData} data
- * Info passed around; key/value store.
- *
- * @typedef FromMarkdownOptions
- * Configuration for how to build mdast.
- * @property {Array<Extension | Array<Extension>> | null | undefined} [mdastExtensions]
- * Extensions for this utility to change how tokens are turned into a tree.
- *
- * @typedef {ParseOptions & FromMarkdownOptions} Options
- * Configuration.
- */
- import {toString} from 'mdast-util-to-string'
- import {parse, postprocess, preprocess} from 'micromark'
- import {decodeNumericCharacterReference} from 'micromark-util-decode-numeric-character-reference'
- import {decodeString} from 'micromark-util-decode-string'
- import {normalizeIdentifier} from 'micromark-util-normalize-identifier'
- import {decodeNamedCharacterReference} from 'decode-named-character-reference'
- import {stringifyPosition} from 'unist-util-stringify-position'
- const own = {}.hasOwnProperty
- /**
- * Turn markdown into a syntax tree.
- *
- * @overload
- * @param {Value} value
- * @param {Encoding | null | undefined} [encoding]
- * @param {Options | null | undefined} [options]
- * @returns {Root}
- *
- * @overload
- * @param {Value} value
- * @param {Options | null | undefined} [options]
- * @returns {Root}
- *
- * @param {Value} value
- * Markdown to parse.
- * @param {Encoding | Options | null | undefined} [encoding]
- * Character encoding for when `value` is `Buffer`.
- * @param {Options | null | undefined} [options]
- * Configuration.
- * @returns {Root}
- * mdast tree.
- */
- export function fromMarkdown(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 | null | undefined} [options]
- */
- function compiler(options) {
- /** @type {Config} */
- const config = {
- 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()
- }
- }
- configure(config, (options || {}).mdastExtensions || [])
- /** @type {CompileData} */
- const data = {}
- return compile
- /**
- * Turn micromark events into an mdast tree.
- *
- * @param {Array<Event>} events
- * Events.
- * @returns {Root}
- * mdast tree.
- */
- function compile(events) {
- /** @type {Root} */
- let tree = {
- type: 'root',
- children: []
- }
- /** @type {Omit<CompileContext, 'sliceSerialize'>} */
- const context = {
- stack: [tree],
- tokenStack: [],
- config,
- enter,
- exit,
- buffer,
- resume,
- data
- }
- /** @type {Array<number>} */
- const listStack = []
- 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 === 'listOrdered' ||
- events[index][1].type === 'listUnordered'
- ) {
- if (events[index][0] === 'enter') {
- listStack.push(index)
- } else {
- const tail = listStack.pop()
- 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]
- )
- }
- }
- // Handle tokens still being open.
- if (context.tokenStack.length > 0) {
- const tail = context.tokenStack[context.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
- }
- )
- }
- // Call transforms.
- index = -1
- while (++index < config.transforms.length) {
- tree = config.transforms[index](tree) || tree
- }
- return tree
- }
- /**
- * @param {Array<Event>} 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]
- switch (event[1].type) {
- case 'listUnordered':
- case 'listOrdered':
- case 'blockQuote': {
- if (event[0] === 'enter') {
- containerBalance++
- } else {
- containerBalance--
- }
- atMarker = undefined
- break
- }
- case 'lineEndingBlank': {
- if (event[0] === 'enter') {
- if (
- listItem &&
- !atMarker &&
- !containerBalance &&
- !firstBlankLineIndex
- ) {
- firstBlankLineIndex = index
- }
- atMarker = undefined
- }
- break
- }
- case 'linePrefix':
- case 'listItemValue':
- case 'listItemMarker':
- case 'listItemPrefix':
- case 'listItemPrefixWhitespace': {
- // Empty.
- break
- }
- default: {
- atMarker = undefined
- }
- }
- if (
- (!containerBalance &&
- event[0] === 'enter' &&
- event[1].type === 'listItemPrefix') ||
- (containerBalance === -1 &&
- event[0] === 'exit' &&
- (event[1].type === 'listUnordered' ||
- event[1].type === 'listOrdered'))
- ) {
- if (listItem) {
- let tailIndex = index
- lineIndex = undefined
- while (tailIndex--) {
- const tailEvent = events[tailIndex]
- if (
- tailEvent[1].type === 'lineEnding' ||
- tailEvent[1].type === 'lineEndingBlank'
- ) {
- if (tailEvent[0] === 'exit') continue
- if (lineIndex) {
- events[lineIndex][1].type = 'lineEndingBlank'
- listSpread = true
- }
- tailEvent[1].type = 'lineEnding'
- lineIndex = tailIndex
- } else if (
- tailEvent[1].type === 'linePrefix' ||
- tailEvent[1].type === 'blockQuotePrefix' ||
- tailEvent[1].type === 'blockQuotePrefixWhitespace' ||
- tailEvent[1].type === 'blockQuoteMarker' ||
- tailEvent[1].type === 'listItemIndent'
- ) {
- // Empty
- } else {
- break
- }
- }
- if (
- firstBlankLineIndex &&
- (!lineIndex || firstBlankLineIndex < lineIndex)
- ) {
- 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 === 'listItemPrefix') {
- /** @type {Token} */
- const item = {
- type: 'listItem',
- _spread: false,
- start: Object.assign({}, event[1].start),
- // @ts-expect-error: we’ll add `end` in a second.
- end: undefined
- }
- listItem = item
- events.splice(index, 0, ['enter', item, event[2]])
- index++
- length++
- firstBlankLineIndex = undefined
- atMarker = true
- }
- }
- }
- events[start][1]._spread = listSpread
- return length
- }
- /**
- * Create an opener handle.
- *
- * @param {(token: Token) => Nodes} create
- * Create a node.
- * @param {Handle | undefined} [and]
- * Optional function to also run.
- * @returns {Handle}
- * Handle.
- */
- function opener(create, and) {
- return open
- /**
- * @this {CompileContext}
- * @param {Token} token
- * @returns {undefined}
- */
- function open(token) {
- enter.call(this, create(token), token)
- if (and) and.call(this, token)
- }
- }
- /**
- * @this {CompileContext}
- * @returns {undefined}
- */
- function buffer() {
- this.stack.push({
- type: 'fragment',
- children: []
- })
- }
- /**
- * @this {CompileContext}
- * Context.
- * @param {Nodes} node
- * Node to enter.
- * @param {Token} token
- * Corresponding token.
- * @param {OnEnterError | undefined} [errorHandler]
- * Handle the case where this token is open, but it is closed by something else.
- * @returns {undefined}
- * Nothing.
- */
- function enter(node, token, errorHandler) {
- const parent = this.stack[this.stack.length - 1]
- /** @type {Array<Nodes>} */
- const siblings = parent.children
- siblings.push(node)
- this.stack.push(node)
- this.tokenStack.push([token, errorHandler])
- node.position = {
- start: point(token.start),
- // @ts-expect-error: `end` will be patched later.
- end: undefined
- }
- }
- /**
- * Create a closer handle.
- *
- * @param {Handle | undefined} [and]
- * Optional function to also run.
- * @returns {Handle}
- * Handle.
- */
- function closer(and) {
- return close
- /**
- * @this {CompileContext}
- * @param {Token} token
- * @returns {undefined}
- */
- function close(token) {
- if (and) and.call(this, token)
- exit.call(this, token)
- }
- }
- /**
- * @this {CompileContext}
- * Context.
- * @param {Token} token
- * Corresponding token.
- * @param {OnExitError | undefined} [onExitError]
- * Handle the case where another token is open.
- * @returns {undefined}
- * Nothing.
- */
- function exit(token, onExitError) {
- const node = this.stack.pop()
- 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])
- }
- }
- node.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @returns {string}
- */
- function resume() {
- return toString(this.stack.pop())
- }
- //
- // Handlers.
- //
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterlistordered() {
- this.data.expectingFirstListItemValue = true
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterlistitemvalue(token) {
- if (this.data.expectingFirstListItemValue) {
- const ancestor = this.stack[this.stack.length - 2]
- ancestor.start = Number.parseInt(this.sliceSerialize(token), 10)
- this.data.expectingFirstListItemValue = undefined
- }
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodefencedfenceinfo() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.lang = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodefencedfencemeta() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.meta = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodefencedfence() {
- // Exit if this is the closing fence.
- if (this.data.flowCodeInside) return
- this.buffer()
- this.data.flowCodeInside = true
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodefenced() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.value = data.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, '')
- this.data.flowCodeInside = undefined
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodeindented() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.value = data.replace(/(\r?\n|\r)$/g, '')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinitionlabelstring(token) {
- const label = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.label = label
- node.identifier = normalizeIdentifier(
- this.sliceSerialize(token)
- ).toLowerCase()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinitiontitlestring() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.title = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinitiondestinationstring() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.url = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitatxheadingsequence(token) {
- const node = this.stack[this.stack.length - 1]
- if (!node.depth) {
- const depth = this.sliceSerialize(token).length
- node.depth = depth
- }
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitsetextheadingtext() {
- this.data.setextHeadingSlurpLineEnding = true
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitsetextheadinglinesequence(token) {
- const node = this.stack[this.stack.length - 1]
- node.depth = this.sliceSerialize(token).codePointAt(0) === 61 ? 1 : 2
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitsetextheading() {
- this.data.setextHeadingSlurpLineEnding = undefined
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterdata(token) {
- const node = this.stack[this.stack.length - 1]
- /** @type {Array<Nodes>} */
- const siblings = node.children
- let tail = siblings[siblings.length - 1]
- if (!tail || tail.type !== 'text') {
- // Add a new text node.
- tail = text()
- tail.position = {
- start: point(token.start),
- // @ts-expect-error: we’ll add `end` later.
- end: undefined
- }
- siblings.push(tail)
- }
- this.stack.push(tail)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdata(token) {
- const tail = this.stack.pop()
- tail.value += this.sliceSerialize(token)
- tail.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlineending(token) {
- const context = this.stack[this.stack.length - 1]
- // If we’re at a hard break, include the line ending in there.
- if (this.data.atHardBreak) {
- const tail = context.children[context.children.length - 1]
- tail.position.end = point(token.end)
- this.data.atHardBreak = undefined
- return
- }
- if (
- !this.data.setextHeadingSlurpLineEnding &&
- config.canContainEols.includes(context.type)
- ) {
- onenterdata.call(this, token)
- onexitdata.call(this, token)
- }
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexithardbreak() {
- this.data.atHardBreak = true
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexithtmlflow() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.value = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexithtmltext() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.value = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodetext() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.value = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlink() {
- const node = this.stack[this.stack.length - 1]
- // Note: there are also `identifier` and `label` fields on this link node!
- // These are used / cleaned here.
- // To do: clean.
- if (this.data.inReference) {
- /** @type {ReferenceType} */
- const referenceType = this.data.referenceType || 'shortcut'
- node.type += 'Reference'
- // @ts-expect-error: mutate.
- node.referenceType = referenceType
- // @ts-expect-error: mutate.
- delete node.url
- delete node.title
- } else {
- // @ts-expect-error: mutate.
- delete node.identifier
- // @ts-expect-error: mutate.
- delete node.label
- }
- this.data.referenceType = undefined
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitimage() {
- const node = this.stack[this.stack.length - 1]
- // Note: there are also `identifier` and `label` fields on this link node!
- // These are used / cleaned here.
- // To do: clean.
- if (this.data.inReference) {
- /** @type {ReferenceType} */
- const referenceType = this.data.referenceType || 'shortcut'
- node.type += 'Reference'
- // @ts-expect-error: mutate.
- node.referenceType = referenceType
- // @ts-expect-error: mutate.
- delete node.url
- delete node.title
- } else {
- // @ts-expect-error: mutate.
- delete node.identifier
- // @ts-expect-error: mutate.
- delete node.label
- }
- this.data.referenceType = undefined
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlabeltext(token) {
- const string = this.sliceSerialize(token)
- const ancestor = this.stack[this.stack.length - 2]
- // @ts-expect-error: stash this on the node, as it might become a reference
- // later.
- ancestor.label = decodeString(string)
- // @ts-expect-error: same as above.
- ancestor.identifier = normalizeIdentifier(string).toLowerCase()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlabel() {
- const fragment = this.stack[this.stack.length - 1]
- const value = this.resume()
- const node = this.stack[this.stack.length - 1]
- // Assume a reference.
- this.data.inReference = true
- if (node.type === 'link') {
- /** @type {Array<PhrasingContent>} */
- const children = fragment.children
- node.children = children
- } else {
- node.alt = value
- }
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitresourcedestinationstring() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.url = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitresourcetitlestring() {
- const data = this.resume()
- const node = this.stack[this.stack.length - 1]
- node.title = data
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitresource() {
- this.data.inReference = undefined
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterreference() {
- this.data.referenceType = 'collapsed'
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitreferencestring(token) {
- const label = this.resume()
- const node = this.stack[this.stack.length - 1]
- // @ts-expect-error: stash this on the node, as it might become a reference
- // later.
- node.label = label
- // @ts-expect-error: same as above.
- node.identifier = normalizeIdentifier(
- this.sliceSerialize(token)
- ).toLowerCase()
- this.data.referenceType = 'full'
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcharacterreferencemarker(token) {
- this.data.characterReferenceType = token.type
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcharacterreferencevalue(token) {
- const data = this.sliceSerialize(token)
- const type = this.data.characterReferenceType
- /** @type {string} */
- let value
- if (type) {
- value = decodeNumericCharacterReference(
- data,
- type === 'characterReferenceMarkerNumeric' ? 10 : 16
- )
- this.data.characterReferenceType = undefined
- } else {
- const result = decodeNamedCharacterReference(data)
- value = result
- }
- const tail = this.stack.pop()
- tail.value += value
- tail.position.end = point(token.end)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitautolinkprotocol(token) {
- onexitdata.call(this, token)
- const node = this.stack[this.stack.length - 1]
- node.url = this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitautolinkemail(token) {
- onexitdata.call(this, token)
- const node = 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() {
- return {
- type: 'heading',
- // @ts-expect-error `depth` will be set later.
- depth: 0,
- 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,
- spread: token._spread,
- children: []
- }
- }
- /**
- * @param {Token} token
- * @returns {ListItem}
- */
- function listItem(token) {
- return {
- type: 'listItem',
- 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'
- }
- }
- }
- /**
- * Copy a point-like value.
- *
- * @param {Point} d
- * Point-like value.
- * @returns {Point}
- * unist point.
- */
- function point(d) {
- return {
- line: d.line,
- column: d.column,
- offset: d.offset
- }
- }
- /**
- * @param {Config} combined
- * @param {Array<Array<Extension> | Extension>} extensions
- * @returns {undefined}
- */
- 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)
- }
- }
- }
- /**
- * @param {Config} combined
- * @param {Extension} extension
- * @returns {undefined}
- */
- function extension(combined, extension) {
- /** @type {keyof Extension} */
- let key
- for (key in extension) {
- if (own.call(extension, key)) {
- switch (key) {
- case 'canContainEols': {
- const right = extension[key]
- if (right) {
- combined[key].push(...right)
- }
- break
- }
- case 'transforms': {
- const right = extension[key]
- if (right) {
- combined[key].push(...right)
- }
- break
- }
- case 'enter':
- case 'exit': {
- const right = extension[key]
- if (right) {
- Object.assign(combined[key], right)
- }
- break
- }
- // No default
- }
- }
- }
- }
- /** @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'
- )
- }
- }
|