index.js 33 KB

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