index.js 28 KB

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