index.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448
  1. /**
  2. * @typedef {import('mdast').Break} Break
  3. * @typedef {import('mdast').Blockquote} Blockquote
  4. * @typedef {import('mdast').Code} Code
  5. * @typedef {import('mdast').Definition} Definition
  6. * @typedef {import('mdast').Emphasis} Emphasis
  7. * @typedef {import('mdast').Heading} Heading
  8. * @typedef {import('mdast').Html} Html
  9. * @typedef {import('mdast').Image} Image
  10. * @typedef {import('mdast').InlineCode} InlineCode
  11. * @typedef {import('mdast').Link} Link
  12. * @typedef {import('mdast').List} List
  13. * @typedef {import('mdast').ListItem} ListItem
  14. * @typedef {import('mdast').Nodes} Nodes
  15. * @typedef {import('mdast').Paragraph} Paragraph
  16. * @typedef {import('mdast').Parent} Parent
  17. * @typedef {import('mdast').PhrasingContent} PhrasingContent
  18. * @typedef {import('mdast').ReferenceType} ReferenceType
  19. * @typedef {import('mdast').Root} Root
  20. * @typedef {import('mdast').Strong} Strong
  21. * @typedef {import('mdast').Text} Text
  22. * @typedef {import('mdast').ThematicBreak} ThematicBreak
  23. *
  24. * @typedef {import('micromark-util-types').Encoding} Encoding
  25. * @typedef {import('micromark-util-types').Event} Event
  26. * @typedef {import('micromark-util-types').ParseOptions} ParseOptions
  27. * @typedef {import('micromark-util-types').Token} Token
  28. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  29. * @typedef {import('micromark-util-types').Value} Value
  30. *
  31. * @typedef {import('unist').Point} Point
  32. *
  33. * @typedef {import('../index.js').CompileData} CompileData
  34. */
  35. /**
  36. * @typedef {Omit<Parent, 'children' | 'type'> & {type: 'fragment', children: Array<PhrasingContent>}} Fragment
  37. */
  38. /**
  39. * @callback Transform
  40. * Extra transform, to change the AST afterwards.
  41. * @param {Root} tree
  42. * Tree to transform.
  43. * @returns {Root | null | undefined | void}
  44. * New tree or nothing (in which case the current tree is used).
  45. *
  46. * @callback Handle
  47. * Handle a token.
  48. * @param {CompileContext} this
  49. * Context.
  50. * @param {Token} token
  51. * Current token.
  52. * @returns {undefined | void}
  53. * Nothing.
  54. *
  55. * @typedef {Record<string, Handle>} Handles
  56. * Token types mapping to handles
  57. *
  58. * @callback OnEnterError
  59. * Handle the case where the `right` token is open, but it is closed (by the
  60. * `left` token) or because we reached the end of the document.
  61. * @param {Omit<CompileContext, 'sliceSerialize'>} this
  62. * Context.
  63. * @param {Token | undefined} left
  64. * Left token.
  65. * @param {Token} right
  66. * Right token.
  67. * @returns {undefined}
  68. * Nothing.
  69. *
  70. * @callback OnExitError
  71. * Handle the case where the `right` token is open but it is closed by
  72. * exiting the `left` token.
  73. * @param {Omit<CompileContext, 'sliceSerialize'>} this
  74. * Context.
  75. * @param {Token} left
  76. * Left token.
  77. * @param {Token} right
  78. * Right token.
  79. * @returns {undefined}
  80. * Nothing.
  81. *
  82. * @typedef {[Token, OnEnterError | undefined]} TokenTuple
  83. * Open token on the stack, with an optional error handler for when
  84. * that token isn’t closed properly.
  85. */
  86. /**
  87. * @typedef Config
  88. * Configuration.
  89. *
  90. * We have our defaults, but extensions will add more.
  91. * @property {Array<string>} canContainEols
  92. * Token types where line endings are used.
  93. * @property {Handles} enter
  94. * Opening handles.
  95. * @property {Handles} exit
  96. * Closing handles.
  97. * @property {Array<Transform>} transforms
  98. * Tree transforms.
  99. *
  100. * @typedef {Partial<Config>} Extension
  101. * Change how markdown tokens from micromark are turned into mdast.
  102. *
  103. * @typedef CompileContext
  104. * mdast compiler context.
  105. * @property {Array<Fragment | Nodes>} stack
  106. * Stack of nodes.
  107. * @property {Array<TokenTuple>} tokenStack
  108. * Stack of tokens.
  109. * @property {(this: CompileContext) => undefined} buffer
  110. * Capture some of the output data.
  111. * @property {(this: CompileContext) => string} resume
  112. * Stop capturing and access the output data.
  113. * @property {(this: CompileContext, node: Nodes, token: Token, onError?: OnEnterError) => undefined} enter
  114. * Enter a node.
  115. * @property {(this: CompileContext, token: Token, onError?: OnExitError) => undefined} exit
  116. * Exit a node.
  117. * @property {TokenizeContext['sliceSerialize']} sliceSerialize
  118. * Get the string value of a token.
  119. * @property {Config} config
  120. * Configuration.
  121. * @property {CompileData} data
  122. * Info passed around; key/value store.
  123. *
  124. * @typedef FromMarkdownOptions
  125. * Configuration for how to build mdast.
  126. * @property {Array<Extension | Array<Extension>> | null | undefined} [mdastExtensions]
  127. * Extensions for this utility to change how tokens are turned into a tree.
  128. *
  129. * @typedef {ParseOptions & FromMarkdownOptions} Options
  130. * Configuration.
  131. */
  132. import {ok as assert} from 'devlop'
  133. import {toString} from 'mdast-util-to-string'
  134. import {parse, postprocess, preprocess} from 'micromark'
  135. import {decodeNumericCharacterReference} from 'micromark-util-decode-numeric-character-reference'
  136. import {decodeString} from 'micromark-util-decode-string'
  137. import {normalizeIdentifier} from 'micromark-util-normalize-identifier'
  138. import {codes, constants, types} from 'micromark-util-symbol'
  139. import {decodeNamedCharacterReference} from 'decode-named-character-reference'
  140. import {stringifyPosition} from 'unist-util-stringify-position'
  141. const own = {}.hasOwnProperty
  142. /**
  143. * Turn markdown into a syntax tree.
  144. *
  145. * @overload
  146. * @param {Value} value
  147. * @param {Encoding | null | undefined} [encoding]
  148. * @param {Options | null | undefined} [options]
  149. * @returns {Root}
  150. *
  151. * @overload
  152. * @param {Value} value
  153. * @param {Options | null | undefined} [options]
  154. * @returns {Root}
  155. *
  156. * @param {Value} value
  157. * Markdown to parse.
  158. * @param {Encoding | Options | null | undefined} [encoding]
  159. * Character encoding for when `value` is `Buffer`.
  160. * @param {Options | null | undefined} [options]
  161. * Configuration.
  162. * @returns {Root}
  163. * mdast tree.
  164. */
  165. export function fromMarkdown(value, encoding, options) {
  166. if (typeof encoding !== 'string') {
  167. options = encoding
  168. encoding = undefined
  169. }
  170. return compiler(options)(
  171. postprocess(
  172. parse(options).document().write(preprocess()(value, encoding, true))
  173. )
  174. )
  175. }
  176. /**
  177. * Note this compiler only understand complete buffering, not streaming.
  178. *
  179. * @param {Options | null | undefined} [options]
  180. */
  181. function compiler(options) {
  182. /** @type {Config} */
  183. const config = {
  184. transforms: [],
  185. canContainEols: ['emphasis', 'fragment', 'heading', 'paragraph', 'strong'],
  186. enter: {
  187. autolink: opener(link),
  188. autolinkProtocol: onenterdata,
  189. autolinkEmail: onenterdata,
  190. atxHeading: opener(heading),
  191. blockQuote: opener(blockQuote),
  192. characterEscape: onenterdata,
  193. characterReference: onenterdata,
  194. codeFenced: opener(codeFlow),
  195. codeFencedFenceInfo: buffer,
  196. codeFencedFenceMeta: buffer,
  197. codeIndented: opener(codeFlow, buffer),
  198. codeText: opener(codeText, buffer),
  199. codeTextData: onenterdata,
  200. data: onenterdata,
  201. codeFlowValue: onenterdata,
  202. definition: opener(definition),
  203. definitionDestinationString: buffer,
  204. definitionLabelString: buffer,
  205. definitionTitleString: buffer,
  206. emphasis: opener(emphasis),
  207. hardBreakEscape: opener(hardBreak),
  208. hardBreakTrailing: opener(hardBreak),
  209. htmlFlow: opener(html, buffer),
  210. htmlFlowData: onenterdata,
  211. htmlText: opener(html, buffer),
  212. htmlTextData: onenterdata,
  213. image: opener(image),
  214. label: buffer,
  215. link: opener(link),
  216. listItem: opener(listItem),
  217. listItemValue: onenterlistitemvalue,
  218. listOrdered: opener(list, onenterlistordered),
  219. listUnordered: opener(list),
  220. paragraph: opener(paragraph),
  221. reference: onenterreference,
  222. referenceString: buffer,
  223. resourceDestinationString: buffer,
  224. resourceTitleString: buffer,
  225. setextHeading: opener(heading),
  226. strong: opener(strong),
  227. thematicBreak: opener(thematicBreak)
  228. },
  229. exit: {
  230. atxHeading: closer(),
  231. atxHeadingSequence: onexitatxheadingsequence,
  232. autolink: closer(),
  233. autolinkEmail: onexitautolinkemail,
  234. autolinkProtocol: onexitautolinkprotocol,
  235. blockQuote: closer(),
  236. characterEscapeValue: onexitdata,
  237. characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
  238. characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
  239. characterReferenceValue: onexitcharacterreferencevalue,
  240. codeFenced: closer(onexitcodefenced),
  241. codeFencedFence: onexitcodefencedfence,
  242. codeFencedFenceInfo: onexitcodefencedfenceinfo,
  243. codeFencedFenceMeta: onexitcodefencedfencemeta,
  244. codeFlowValue: onexitdata,
  245. codeIndented: closer(onexitcodeindented),
  246. codeText: closer(onexitcodetext),
  247. codeTextData: onexitdata,
  248. data: onexitdata,
  249. definition: closer(),
  250. definitionDestinationString: onexitdefinitiondestinationstring,
  251. definitionLabelString: onexitdefinitionlabelstring,
  252. definitionTitleString: onexitdefinitiontitlestring,
  253. emphasis: closer(),
  254. hardBreakEscape: closer(onexithardbreak),
  255. hardBreakTrailing: closer(onexithardbreak),
  256. htmlFlow: closer(onexithtmlflow),
  257. htmlFlowData: onexitdata,
  258. htmlText: closer(onexithtmltext),
  259. htmlTextData: onexitdata,
  260. image: closer(onexitimage),
  261. label: onexitlabel,
  262. labelText: onexitlabeltext,
  263. lineEnding: onexitlineending,
  264. link: closer(onexitlink),
  265. listItem: closer(),
  266. listOrdered: closer(),
  267. listUnordered: closer(),
  268. paragraph: closer(),
  269. referenceString: onexitreferencestring,
  270. resourceDestinationString: onexitresourcedestinationstring,
  271. resourceTitleString: onexitresourcetitlestring,
  272. resource: onexitresource,
  273. setextHeading: closer(onexitsetextheading),
  274. setextHeadingLineSequence: onexitsetextheadinglinesequence,
  275. setextHeadingText: onexitsetextheadingtext,
  276. strong: closer(),
  277. thematicBreak: closer()
  278. }
  279. }
  280. configure(config, (options || {}).mdastExtensions || [])
  281. /** @type {CompileData} */
  282. const data = {}
  283. return compile
  284. /**
  285. * Turn micromark events into an mdast tree.
  286. *
  287. * @param {Array<Event>} events
  288. * Events.
  289. * @returns {Root}
  290. * mdast tree.
  291. */
  292. function compile(events) {
  293. /** @type {Root} */
  294. let tree = {type: 'root', children: []}
  295. /** @type {Omit<CompileContext, 'sliceSerialize'>} */
  296. const context = {
  297. stack: [tree],
  298. tokenStack: [],
  299. config,
  300. enter,
  301. exit,
  302. buffer,
  303. resume,
  304. data
  305. }
  306. /** @type {Array<number>} */
  307. const listStack = []
  308. let index = -1
  309. while (++index < events.length) {
  310. // We preprocess lists to add `listItem` tokens, and to infer whether
  311. // items the list itself are spread out.
  312. if (
  313. events[index][1].type === types.listOrdered ||
  314. events[index][1].type === types.listUnordered
  315. ) {
  316. if (events[index][0] === 'enter') {
  317. listStack.push(index)
  318. } else {
  319. const tail = listStack.pop()
  320. assert(typeof tail === 'number', 'expected list ot be open')
  321. index = prepareList(events, tail, index)
  322. }
  323. }
  324. }
  325. index = -1
  326. while (++index < events.length) {
  327. const handler = config[events[index][0]]
  328. if (own.call(handler, events[index][1].type)) {
  329. handler[events[index][1].type].call(
  330. Object.assign(
  331. {sliceSerialize: events[index][2].sliceSerialize},
  332. context
  333. ),
  334. events[index][1]
  335. )
  336. }
  337. }
  338. // Handle tokens still being open.
  339. if (context.tokenStack.length > 0) {
  340. const tail = context.tokenStack[context.tokenStack.length - 1]
  341. const handler = tail[1] || defaultOnError
  342. handler.call(context, undefined, tail[0])
  343. }
  344. // Figure out `root` position.
  345. tree.position = {
  346. start: point(
  347. events.length > 0 ? events[0][1].start : {line: 1, column: 1, offset: 0}
  348. ),
  349. end: point(
  350. events.length > 0
  351. ? events[events.length - 2][1].end
  352. : {line: 1, column: 1, offset: 0}
  353. )
  354. }
  355. // Call transforms.
  356. index = -1
  357. while (++index < config.transforms.length) {
  358. tree = config.transforms[index](tree) || tree
  359. }
  360. return tree
  361. }
  362. /**
  363. * @param {Array<Event>} events
  364. * @param {number} start
  365. * @param {number} length
  366. * @returns {number}
  367. */
  368. function prepareList(events, start, length) {
  369. let index = start - 1
  370. let containerBalance = -1
  371. let listSpread = false
  372. /** @type {Token | undefined} */
  373. let listItem
  374. /** @type {number | undefined} */
  375. let lineIndex
  376. /** @type {number | undefined} */
  377. let firstBlankLineIndex
  378. /** @type {boolean | undefined} */
  379. let atMarker
  380. while (++index <= length) {
  381. const event = events[index]
  382. switch (event[1].type) {
  383. case types.listUnordered:
  384. case types.listOrdered:
  385. case types.blockQuote: {
  386. if (event[0] === 'enter') {
  387. containerBalance++
  388. } else {
  389. containerBalance--
  390. }
  391. atMarker = undefined
  392. break
  393. }
  394. case types.lineEndingBlank: {
  395. if (event[0] === 'enter') {
  396. if (
  397. listItem &&
  398. !atMarker &&
  399. !containerBalance &&
  400. !firstBlankLineIndex
  401. ) {
  402. firstBlankLineIndex = index
  403. }
  404. atMarker = undefined
  405. }
  406. break
  407. }
  408. case types.linePrefix:
  409. case types.listItemValue:
  410. case types.listItemMarker:
  411. case types.listItemPrefix:
  412. case types.listItemPrefixWhitespace: {
  413. // Empty.
  414. break
  415. }
  416. default: {
  417. atMarker = undefined
  418. }
  419. }
  420. if (
  421. (!containerBalance &&
  422. event[0] === 'enter' &&
  423. event[1].type === types.listItemPrefix) ||
  424. (containerBalance === -1 &&
  425. event[0] === 'exit' &&
  426. (event[1].type === types.listUnordered ||
  427. event[1].type === types.listOrdered))
  428. ) {
  429. if (listItem) {
  430. let tailIndex = index
  431. lineIndex = undefined
  432. while (tailIndex--) {
  433. const tailEvent = events[tailIndex]
  434. if (
  435. tailEvent[1].type === types.lineEnding ||
  436. tailEvent[1].type === types.lineEndingBlank
  437. ) {
  438. if (tailEvent[0] === 'exit') continue
  439. if (lineIndex) {
  440. events[lineIndex][1].type = types.lineEndingBlank
  441. listSpread = true
  442. }
  443. tailEvent[1].type = types.lineEnding
  444. lineIndex = tailIndex
  445. } else if (
  446. tailEvent[1].type === types.linePrefix ||
  447. tailEvent[1].type === types.blockQuotePrefix ||
  448. tailEvent[1].type === types.blockQuotePrefixWhitespace ||
  449. tailEvent[1].type === types.blockQuoteMarker ||
  450. tailEvent[1].type === types.listItemIndent
  451. ) {
  452. // Empty
  453. } else {
  454. break
  455. }
  456. }
  457. if (
  458. firstBlankLineIndex &&
  459. (!lineIndex || firstBlankLineIndex < lineIndex)
  460. ) {
  461. listItem._spread = true
  462. }
  463. // Fix position.
  464. listItem.end = Object.assign(
  465. {},
  466. lineIndex ? events[lineIndex][1].start : event[1].end
  467. )
  468. events.splice(lineIndex || index, 0, ['exit', listItem, event[2]])
  469. index++
  470. length++
  471. }
  472. // Create a new list item.
  473. if (event[1].type === types.listItemPrefix) {
  474. /** @type {Token} */
  475. const item = {
  476. type: 'listItem',
  477. _spread: false,
  478. start: Object.assign({}, event[1].start),
  479. // @ts-expect-error: we’ll add `end` in a second.
  480. end: undefined
  481. }
  482. listItem = item
  483. events.splice(index, 0, ['enter', item, event[2]])
  484. index++
  485. length++
  486. firstBlankLineIndex = undefined
  487. atMarker = true
  488. }
  489. }
  490. }
  491. events[start][1]._spread = listSpread
  492. return length
  493. }
  494. /**
  495. * Create an opener handle.
  496. *
  497. * @param {(token: Token) => Nodes} create
  498. * Create a node.
  499. * @param {Handle | undefined} [and]
  500. * Optional function to also run.
  501. * @returns {Handle}
  502. * Handle.
  503. */
  504. function opener(create, and) {
  505. return open
  506. /**
  507. * @this {CompileContext}
  508. * @param {Token} token
  509. * @returns {undefined}
  510. */
  511. function open(token) {
  512. enter.call(this, create(token), token)
  513. if (and) and.call(this, token)
  514. }
  515. }
  516. /**
  517. * @this {CompileContext}
  518. * @returns {undefined}
  519. */
  520. function buffer() {
  521. this.stack.push({type: 'fragment', children: []})
  522. }
  523. /**
  524. * @this {CompileContext}
  525. * Context.
  526. * @param {Nodes} node
  527. * Node to enter.
  528. * @param {Token} token
  529. * Corresponding token.
  530. * @param {OnEnterError | undefined} [errorHandler]
  531. * Handle the case where this token is open, but it is closed by something else.
  532. * @returns {undefined}
  533. * Nothing.
  534. */
  535. function enter(node, token, errorHandler) {
  536. const parent = this.stack[this.stack.length - 1]
  537. assert(parent, 'expected `parent`')
  538. assert('children' in parent, 'expected `parent`')
  539. /** @type {Array<Nodes>} */
  540. const siblings = parent.children
  541. siblings.push(node)
  542. this.stack.push(node)
  543. this.tokenStack.push([token, errorHandler])
  544. node.position = {
  545. start: point(token.start),
  546. // @ts-expect-error: `end` will be patched later.
  547. end: undefined
  548. }
  549. }
  550. /**
  551. * Create a closer handle.
  552. *
  553. * @param {Handle | undefined} [and]
  554. * Optional function to also run.
  555. * @returns {Handle}
  556. * Handle.
  557. */
  558. function closer(and) {
  559. return close
  560. /**
  561. * @this {CompileContext}
  562. * @param {Token} token
  563. * @returns {undefined}
  564. */
  565. function close(token) {
  566. if (and) and.call(this, token)
  567. exit.call(this, token)
  568. }
  569. }
  570. /**
  571. * @this {CompileContext}
  572. * Context.
  573. * @param {Token} token
  574. * Corresponding token.
  575. * @param {OnExitError | undefined} [onExitError]
  576. * Handle the case where another token is open.
  577. * @returns {undefined}
  578. * Nothing.
  579. */
  580. function exit(token, onExitError) {
  581. const node = this.stack.pop()
  582. assert(node, 'expected `node`')
  583. const open = this.tokenStack.pop()
  584. if (!open) {
  585. throw new Error(
  586. 'Cannot close `' +
  587. token.type +
  588. '` (' +
  589. stringifyPosition({start: token.start, end: token.end}) +
  590. '): it’s not open'
  591. )
  592. } else if (open[0].type !== token.type) {
  593. if (onExitError) {
  594. onExitError.call(this, token, open[0])
  595. } else {
  596. const handler = open[1] || defaultOnError
  597. handler.call(this, token, open[0])
  598. }
  599. }
  600. assert(node.type !== 'fragment', 'unexpected fragment `exit`ed')
  601. assert(node.position, 'expected `position` to be defined')
  602. node.position.end = point(token.end)
  603. }
  604. /**
  605. * @this {CompileContext}
  606. * @returns {string}
  607. */
  608. function resume() {
  609. return toString(this.stack.pop())
  610. }
  611. //
  612. // Handlers.
  613. //
  614. /**
  615. * @this {CompileContext}
  616. * @type {Handle}
  617. */
  618. function onenterlistordered() {
  619. this.data.expectingFirstListItemValue = true
  620. }
  621. /**
  622. * @this {CompileContext}
  623. * @type {Handle}
  624. */
  625. function onenterlistitemvalue(token) {
  626. if (this.data.expectingFirstListItemValue) {
  627. const ancestor = this.stack[this.stack.length - 2]
  628. assert(ancestor, 'expected nodes on stack')
  629. assert(ancestor.type === 'list', 'expected list on stack')
  630. ancestor.start = Number.parseInt(
  631. this.sliceSerialize(token),
  632. constants.numericBaseDecimal
  633. )
  634. this.data.expectingFirstListItemValue = undefined
  635. }
  636. }
  637. /**
  638. * @this {CompileContext}
  639. * @type {Handle}
  640. */
  641. function onexitcodefencedfenceinfo() {
  642. const data = this.resume()
  643. const node = this.stack[this.stack.length - 1]
  644. assert(node, 'expected node on stack')
  645. assert(node.type === 'code', 'expected code on stack')
  646. node.lang = data
  647. }
  648. /**
  649. * @this {CompileContext}
  650. * @type {Handle}
  651. */
  652. function onexitcodefencedfencemeta() {
  653. const data = this.resume()
  654. const node = this.stack[this.stack.length - 1]
  655. assert(node, 'expected node on stack')
  656. assert(node.type === 'code', 'expected code on stack')
  657. node.meta = data
  658. }
  659. /**
  660. * @this {CompileContext}
  661. * @type {Handle}
  662. */
  663. function onexitcodefencedfence() {
  664. // Exit if this is the closing fence.
  665. if (this.data.flowCodeInside) return
  666. this.buffer()
  667. this.data.flowCodeInside = true
  668. }
  669. /**
  670. * @this {CompileContext}
  671. * @type {Handle}
  672. */
  673. function onexitcodefenced() {
  674. const data = this.resume()
  675. const node = this.stack[this.stack.length - 1]
  676. assert(node, 'expected node on stack')
  677. assert(node.type === 'code', 'expected code on stack')
  678. node.value = data.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, '')
  679. this.data.flowCodeInside = undefined
  680. }
  681. /**
  682. * @this {CompileContext}
  683. * @type {Handle}
  684. */
  685. function onexitcodeindented() {
  686. const data = this.resume()
  687. const node = this.stack[this.stack.length - 1]
  688. assert(node, 'expected node on stack')
  689. assert(node.type === 'code', 'expected code on stack')
  690. node.value = data.replace(/(\r?\n|\r)$/g, '')
  691. }
  692. /**
  693. * @this {CompileContext}
  694. * @type {Handle}
  695. */
  696. function onexitdefinitionlabelstring(token) {
  697. const label = this.resume()
  698. const node = this.stack[this.stack.length - 1]
  699. assert(node, 'expected node on stack')
  700. assert(node.type === 'definition', 'expected definition on stack')
  701. node.label = label
  702. node.identifier = normalizeIdentifier(
  703. this.sliceSerialize(token)
  704. ).toLowerCase()
  705. }
  706. /**
  707. * @this {CompileContext}
  708. * @type {Handle}
  709. */
  710. function onexitdefinitiontitlestring() {
  711. const data = this.resume()
  712. const node = this.stack[this.stack.length - 1]
  713. assert(node, 'expected node on stack')
  714. assert(node.type === 'definition', 'expected definition on stack')
  715. node.title = data
  716. }
  717. /**
  718. * @this {CompileContext}
  719. * @type {Handle}
  720. */
  721. function onexitdefinitiondestinationstring() {
  722. const data = this.resume()
  723. const node = this.stack[this.stack.length - 1]
  724. assert(node, 'expected node on stack')
  725. assert(node.type === 'definition', 'expected definition on stack')
  726. node.url = data
  727. }
  728. /**
  729. * @this {CompileContext}
  730. * @type {Handle}
  731. */
  732. function onexitatxheadingsequence(token) {
  733. const node = this.stack[this.stack.length - 1]
  734. assert(node, 'expected node on stack')
  735. assert(node.type === 'heading', 'expected heading on stack')
  736. if (!node.depth) {
  737. const depth = this.sliceSerialize(token).length
  738. assert(
  739. depth === 1 ||
  740. depth === 2 ||
  741. depth === 3 ||
  742. depth === 4 ||
  743. depth === 5 ||
  744. depth === 6,
  745. 'expected `depth` between `1` and `6`'
  746. )
  747. node.depth = depth
  748. }
  749. }
  750. /**
  751. * @this {CompileContext}
  752. * @type {Handle}
  753. */
  754. function onexitsetextheadingtext() {
  755. this.data.setextHeadingSlurpLineEnding = true
  756. }
  757. /**
  758. * @this {CompileContext}
  759. * @type {Handle}
  760. */
  761. function onexitsetextheadinglinesequence(token) {
  762. const node = this.stack[this.stack.length - 1]
  763. assert(node, 'expected node on stack')
  764. assert(node.type === 'heading', 'expected heading on stack')
  765. node.depth =
  766. this.sliceSerialize(token).codePointAt(0) === codes.equalsTo ? 1 : 2
  767. }
  768. /**
  769. * @this {CompileContext}
  770. * @type {Handle}
  771. */
  772. function onexitsetextheading() {
  773. this.data.setextHeadingSlurpLineEnding = undefined
  774. }
  775. /**
  776. * @this {CompileContext}
  777. * @type {Handle}
  778. */
  779. function onenterdata(token) {
  780. const node = this.stack[this.stack.length - 1]
  781. assert(node, 'expected node on stack')
  782. assert('children' in node, 'expected parent on stack')
  783. /** @type {Array<Nodes>} */
  784. const siblings = node.children
  785. let tail = siblings[siblings.length - 1]
  786. if (!tail || tail.type !== 'text') {
  787. // Add a new text node.
  788. tail = text()
  789. tail.position = {
  790. start: point(token.start),
  791. // @ts-expect-error: we’ll add `end` later.
  792. end: undefined
  793. }
  794. siblings.push(tail)
  795. }
  796. this.stack.push(tail)
  797. }
  798. /**
  799. * @this {CompileContext}
  800. * @type {Handle}
  801. */
  802. function onexitdata(token) {
  803. const tail = this.stack.pop()
  804. assert(tail, 'expected a `node` to be on the stack')
  805. assert('value' in tail, 'expected a `literal` to be on the stack')
  806. assert(tail.position, 'expected `node` to have an open position')
  807. tail.value += this.sliceSerialize(token)
  808. tail.position.end = point(token.end)
  809. }
  810. /**
  811. * @this {CompileContext}
  812. * @type {Handle}
  813. */
  814. function onexitlineending(token) {
  815. const context = this.stack[this.stack.length - 1]
  816. assert(context, 'expected `node`')
  817. // If we’re at a hard break, include the line ending in there.
  818. if (this.data.atHardBreak) {
  819. assert('children' in context, 'expected `parent`')
  820. const tail = context.children[context.children.length - 1]
  821. assert(tail.position, 'expected tail to have a starting position')
  822. tail.position.end = point(token.end)
  823. this.data.atHardBreak = undefined
  824. return
  825. }
  826. if (
  827. !this.data.setextHeadingSlurpLineEnding &&
  828. config.canContainEols.includes(context.type)
  829. ) {
  830. onenterdata.call(this, token)
  831. onexitdata.call(this, token)
  832. }
  833. }
  834. /**
  835. * @this {CompileContext}
  836. * @type {Handle}
  837. */
  838. function onexithardbreak() {
  839. this.data.atHardBreak = true
  840. }
  841. /**
  842. * @this {CompileContext}
  843. * @type {Handle}
  844. */
  845. function onexithtmlflow() {
  846. const data = this.resume()
  847. const node = this.stack[this.stack.length - 1]
  848. assert(node, 'expected node on stack')
  849. assert(node.type === 'html', 'expected html on stack')
  850. node.value = data
  851. }
  852. /**
  853. * @this {CompileContext}
  854. * @type {Handle}
  855. */
  856. function onexithtmltext() {
  857. const data = this.resume()
  858. const node = this.stack[this.stack.length - 1]
  859. assert(node, 'expected node on stack')
  860. assert(node.type === 'html', 'expected html on stack')
  861. node.value = data
  862. }
  863. /**
  864. * @this {CompileContext}
  865. * @type {Handle}
  866. */
  867. function onexitcodetext() {
  868. const data = this.resume()
  869. const node = this.stack[this.stack.length - 1]
  870. assert(node, 'expected node on stack')
  871. assert(node.type === 'inlineCode', 'expected inline code on stack')
  872. node.value = data
  873. }
  874. /**
  875. * @this {CompileContext}
  876. * @type {Handle}
  877. */
  878. function onexitlink() {
  879. const node = this.stack[this.stack.length - 1]
  880. assert(node, 'expected node on stack')
  881. assert(node.type === 'link', 'expected link on stack')
  882. // Note: there are also `identifier` and `label` fields on this link node!
  883. // These are used / cleaned here.
  884. // To do: clean.
  885. if (this.data.inReference) {
  886. /** @type {ReferenceType} */
  887. const referenceType = this.data.referenceType || 'shortcut'
  888. node.type += 'Reference'
  889. // @ts-expect-error: mutate.
  890. node.referenceType = referenceType
  891. // @ts-expect-error: mutate.
  892. delete node.url
  893. delete node.title
  894. } else {
  895. // @ts-expect-error: mutate.
  896. delete node.identifier
  897. // @ts-expect-error: mutate.
  898. delete node.label
  899. }
  900. this.data.referenceType = undefined
  901. }
  902. /**
  903. * @this {CompileContext}
  904. * @type {Handle}
  905. */
  906. function onexitimage() {
  907. const node = this.stack[this.stack.length - 1]
  908. assert(node, 'expected node on stack')
  909. assert(node.type === 'image', 'expected image on stack')
  910. // Note: there are also `identifier` and `label` fields on this link node!
  911. // These are used / cleaned here.
  912. // To do: clean.
  913. if (this.data.inReference) {
  914. /** @type {ReferenceType} */
  915. const referenceType = this.data.referenceType || 'shortcut'
  916. node.type += 'Reference'
  917. // @ts-expect-error: mutate.
  918. node.referenceType = referenceType
  919. // @ts-expect-error: mutate.
  920. delete node.url
  921. delete node.title
  922. } else {
  923. // @ts-expect-error: mutate.
  924. delete node.identifier
  925. // @ts-expect-error: mutate.
  926. delete node.label
  927. }
  928. this.data.referenceType = undefined
  929. }
  930. /**
  931. * @this {CompileContext}
  932. * @type {Handle}
  933. */
  934. function onexitlabeltext(token) {
  935. const string = this.sliceSerialize(token)
  936. const ancestor = this.stack[this.stack.length - 2]
  937. assert(ancestor, 'expected ancestor on stack')
  938. assert(
  939. ancestor.type === 'image' || ancestor.type === 'link',
  940. 'expected image or link on stack'
  941. )
  942. // @ts-expect-error: stash this on the node, as it might become a reference
  943. // later.
  944. ancestor.label = decodeString(string)
  945. // @ts-expect-error: same as above.
  946. ancestor.identifier = normalizeIdentifier(string).toLowerCase()
  947. }
  948. /**
  949. * @this {CompileContext}
  950. * @type {Handle}
  951. */
  952. function onexitlabel() {
  953. const fragment = this.stack[this.stack.length - 1]
  954. assert(fragment, 'expected node on stack')
  955. assert(fragment.type === 'fragment', 'expected fragment on stack')
  956. const value = this.resume()
  957. const node = this.stack[this.stack.length - 1]
  958. assert(node, 'expected node on stack')
  959. assert(
  960. node.type === 'image' || node.type === 'link',
  961. 'expected image or link on stack'
  962. )
  963. // Assume a reference.
  964. this.data.inReference = true
  965. if (node.type === 'link') {
  966. /** @type {Array<PhrasingContent>} */
  967. const children = fragment.children
  968. node.children = children
  969. } else {
  970. node.alt = value
  971. }
  972. }
  973. /**
  974. * @this {CompileContext}
  975. * @type {Handle}
  976. */
  977. function onexitresourcedestinationstring() {
  978. const data = this.resume()
  979. const node = this.stack[this.stack.length - 1]
  980. assert(node, 'expected node on stack')
  981. assert(
  982. node.type === 'image' || node.type === 'link',
  983. 'expected image or link on stack'
  984. )
  985. node.url = data
  986. }
  987. /**
  988. * @this {CompileContext}
  989. * @type {Handle}
  990. */
  991. function onexitresourcetitlestring() {
  992. const data = this.resume()
  993. const node = this.stack[this.stack.length - 1]
  994. assert(node, 'expected node on stack')
  995. assert(
  996. node.type === 'image' || node.type === 'link',
  997. 'expected image or link on stack'
  998. )
  999. node.title = data
  1000. }
  1001. /**
  1002. * @this {CompileContext}
  1003. * @type {Handle}
  1004. */
  1005. function onexitresource() {
  1006. this.data.inReference = undefined
  1007. }
  1008. /**
  1009. * @this {CompileContext}
  1010. * @type {Handle}
  1011. */
  1012. function onenterreference() {
  1013. this.data.referenceType = 'collapsed'
  1014. }
  1015. /**
  1016. * @this {CompileContext}
  1017. * @type {Handle}
  1018. */
  1019. function onexitreferencestring(token) {
  1020. const label = this.resume()
  1021. const node = this.stack[this.stack.length - 1]
  1022. assert(node, 'expected node on stack')
  1023. assert(
  1024. node.type === 'image' || node.type === 'link',
  1025. 'expected image reference or link reference on stack'
  1026. )
  1027. // @ts-expect-error: stash this on the node, as it might become a reference
  1028. // later.
  1029. node.label = label
  1030. // @ts-expect-error: same as above.
  1031. node.identifier = normalizeIdentifier(
  1032. this.sliceSerialize(token)
  1033. ).toLowerCase()
  1034. this.data.referenceType = 'full'
  1035. }
  1036. /**
  1037. * @this {CompileContext}
  1038. * @type {Handle}
  1039. */
  1040. function onexitcharacterreferencemarker(token) {
  1041. assert(
  1042. token.type === 'characterReferenceMarkerNumeric' ||
  1043. token.type === 'characterReferenceMarkerHexadecimal'
  1044. )
  1045. this.data.characterReferenceType = token.type
  1046. }
  1047. /**
  1048. * @this {CompileContext}
  1049. * @type {Handle}
  1050. */
  1051. function onexitcharacterreferencevalue(token) {
  1052. const data = this.sliceSerialize(token)
  1053. const type = this.data.characterReferenceType
  1054. /** @type {string} */
  1055. let value
  1056. if (type) {
  1057. value = decodeNumericCharacterReference(
  1058. data,
  1059. type === types.characterReferenceMarkerNumeric
  1060. ? constants.numericBaseDecimal
  1061. : constants.numericBaseHexadecimal
  1062. )
  1063. this.data.characterReferenceType = undefined
  1064. } else {
  1065. const result = decodeNamedCharacterReference(data)
  1066. assert(result !== false, 'expected reference to decode')
  1067. value = result
  1068. }
  1069. const tail = this.stack.pop()
  1070. assert(tail, 'expected `node`')
  1071. assert(tail.position, 'expected `node.position`')
  1072. assert('value' in tail, 'expected `node.value`')
  1073. tail.value += value
  1074. tail.position.end = point(token.end)
  1075. }
  1076. /**
  1077. * @this {CompileContext}
  1078. * @type {Handle}
  1079. */
  1080. function onexitautolinkprotocol(token) {
  1081. onexitdata.call(this, token)
  1082. const node = this.stack[this.stack.length - 1]
  1083. assert(node, 'expected node on stack')
  1084. assert(node.type === 'link', 'expected link on stack')
  1085. node.url = this.sliceSerialize(token)
  1086. }
  1087. /**
  1088. * @this {CompileContext}
  1089. * @type {Handle}
  1090. */
  1091. function onexitautolinkemail(token) {
  1092. onexitdata.call(this, token)
  1093. const node = this.stack[this.stack.length - 1]
  1094. assert(node, 'expected node on stack')
  1095. assert(node.type === 'link', 'expected link on stack')
  1096. node.url = 'mailto:' + this.sliceSerialize(token)
  1097. }
  1098. //
  1099. // Creaters.
  1100. //
  1101. /** @returns {Blockquote} */
  1102. function blockQuote() {
  1103. return {type: 'blockquote', children: []}
  1104. }
  1105. /** @returns {Code} */
  1106. function codeFlow() {
  1107. return {type: 'code', lang: null, meta: null, value: ''}
  1108. }
  1109. /** @returns {InlineCode} */
  1110. function codeText() {
  1111. return {type: 'inlineCode', value: ''}
  1112. }
  1113. /** @returns {Definition} */
  1114. function definition() {
  1115. return {
  1116. type: 'definition',
  1117. identifier: '',
  1118. label: null,
  1119. title: null,
  1120. url: ''
  1121. }
  1122. }
  1123. /** @returns {Emphasis} */
  1124. function emphasis() {
  1125. return {type: 'emphasis', children: []}
  1126. }
  1127. /** @returns {Heading} */
  1128. function heading() {
  1129. return {
  1130. type: 'heading',
  1131. // @ts-expect-error `depth` will be set later.
  1132. depth: 0,
  1133. children: []
  1134. }
  1135. }
  1136. /** @returns {Break} */
  1137. function hardBreak() {
  1138. return {type: 'break'}
  1139. }
  1140. /** @returns {Html} */
  1141. function html() {
  1142. return {type: 'html', value: ''}
  1143. }
  1144. /** @returns {Image} */
  1145. function image() {
  1146. return {type: 'image', title: null, url: '', alt: null}
  1147. }
  1148. /** @returns {Link} */
  1149. function link() {
  1150. return {type: 'link', title: null, url: '', children: []}
  1151. }
  1152. /**
  1153. * @param {Token} token
  1154. * @returns {List}
  1155. */
  1156. function list(token) {
  1157. return {
  1158. type: 'list',
  1159. ordered: token.type === 'listOrdered',
  1160. start: null,
  1161. spread: token._spread,
  1162. children: []
  1163. }
  1164. }
  1165. /**
  1166. * @param {Token} token
  1167. * @returns {ListItem}
  1168. */
  1169. function listItem(token) {
  1170. return {
  1171. type: 'listItem',
  1172. spread: token._spread,
  1173. checked: null,
  1174. children: []
  1175. }
  1176. }
  1177. /** @returns {Paragraph} */
  1178. function paragraph() {
  1179. return {type: 'paragraph', children: []}
  1180. }
  1181. /** @returns {Strong} */
  1182. function strong() {
  1183. return {type: 'strong', children: []}
  1184. }
  1185. /** @returns {Text} */
  1186. function text() {
  1187. return {type: 'text', value: ''}
  1188. }
  1189. /** @returns {ThematicBreak} */
  1190. function thematicBreak() {
  1191. return {type: 'thematicBreak'}
  1192. }
  1193. }
  1194. /**
  1195. * Copy a point-like value.
  1196. *
  1197. * @param {Point} d
  1198. * Point-like value.
  1199. * @returns {Point}
  1200. * unist point.
  1201. */
  1202. function point(d) {
  1203. return {line: d.line, column: d.column, offset: d.offset}
  1204. }
  1205. /**
  1206. * @param {Config} combined
  1207. * @param {Array<Array<Extension> | Extension>} extensions
  1208. * @returns {undefined}
  1209. */
  1210. function configure(combined, extensions) {
  1211. let index = -1
  1212. while (++index < extensions.length) {
  1213. const value = extensions[index]
  1214. if (Array.isArray(value)) {
  1215. configure(combined, value)
  1216. } else {
  1217. extension(combined, value)
  1218. }
  1219. }
  1220. }
  1221. /**
  1222. * @param {Config} combined
  1223. * @param {Extension} extension
  1224. * @returns {undefined}
  1225. */
  1226. function extension(combined, extension) {
  1227. /** @type {keyof Extension} */
  1228. let key
  1229. for (key in extension) {
  1230. if (own.call(extension, key)) {
  1231. switch (key) {
  1232. case 'canContainEols': {
  1233. const right = extension[key]
  1234. if (right) {
  1235. combined[key].push(...right)
  1236. }
  1237. break
  1238. }
  1239. case 'transforms': {
  1240. const right = extension[key]
  1241. if (right) {
  1242. combined[key].push(...right)
  1243. }
  1244. break
  1245. }
  1246. case 'enter':
  1247. case 'exit': {
  1248. const right = extension[key]
  1249. if (right) {
  1250. Object.assign(combined[key], right)
  1251. }
  1252. break
  1253. }
  1254. // No default
  1255. }
  1256. }
  1257. }
  1258. }
  1259. /** @type {OnEnterError} */
  1260. function defaultOnError(left, right) {
  1261. if (left) {
  1262. throw new Error(
  1263. 'Cannot close `' +
  1264. left.type +
  1265. '` (' +
  1266. stringifyPosition({start: left.start, end: left.end}) +
  1267. '): a different token (`' +
  1268. right.type +
  1269. '`, ' +
  1270. stringifyPosition({start: right.start, end: right.end}) +
  1271. ') is open'
  1272. )
  1273. } else {
  1274. throw new Error(
  1275. 'Cannot close document, a token (`' +
  1276. right.type +
  1277. '`, ' +
  1278. stringifyPosition({start: right.start, end: right.end}) +
  1279. ') is still open'
  1280. )
  1281. }
  1282. }