index.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /**
  2. * @typedef {import('mdast').Nodes} Nodes
  3. * @typedef {import('./types.js').Enter} Enter
  4. * @typedef {import('./types.js').Info} Info
  5. * @typedef {import('./types.js').Join} Join
  6. * @typedef {import('./types.js').FlowParents} FlowParents
  7. * @typedef {import('./types.js').Options} Options
  8. * @typedef {import('./types.js').PhrasingParents} PhrasingParents
  9. * @typedef {import('./types.js').SafeConfig} SafeConfig
  10. * @typedef {import('./types.js').State} State
  11. * @typedef {import('./types.js').TrackFields} TrackFields
  12. */
  13. import {zwitch} from 'zwitch'
  14. import {configure} from './configure.js'
  15. import {handle as handlers} from './handle/index.js'
  16. import {join} from './join.js'
  17. import {unsafe} from './unsafe.js'
  18. import {association} from './util/association.js'
  19. import {compilePattern} from './util/compile-pattern.js'
  20. import {containerPhrasing} from './util/container-phrasing.js'
  21. import {containerFlow} from './util/container-flow.js'
  22. import {indentLines} from './util/indent-lines.js'
  23. import {safe} from './util/safe.js'
  24. import {track} from './util/track.js'
  25. /**
  26. * Turn an mdast syntax tree into markdown.
  27. *
  28. * @param {Nodes} tree
  29. * Tree to serialize.
  30. * @param {Options} [options]
  31. * Configuration (optional).
  32. * @returns {string}
  33. * Serialized markdown representing `tree`.
  34. */
  35. export function toMarkdown(tree, options = {}) {
  36. /** @type {State} */
  37. const state = {
  38. enter,
  39. indentLines,
  40. associationId: association,
  41. containerPhrasing: containerPhrasingBound,
  42. containerFlow: containerFlowBound,
  43. createTracker: track,
  44. compilePattern,
  45. safe: safeBound,
  46. stack: [],
  47. unsafe: [...unsafe],
  48. join: [...join],
  49. // @ts-expect-error: GFM / frontmatter are typed in `mdast` but not defined
  50. // here.
  51. handlers: {...handlers},
  52. options: {},
  53. indexStack: [],
  54. // @ts-expect-error: add `handle` in a second.
  55. handle: undefined
  56. }
  57. configure(state, options)
  58. if (state.options.tightDefinitions) {
  59. state.join.push(joinDefinition)
  60. }
  61. state.handle = zwitch('type', {
  62. invalid,
  63. unknown,
  64. handlers: state.handlers
  65. })
  66. let result = state.handle(tree, undefined, state, {
  67. before: '\n',
  68. after: '\n',
  69. now: {line: 1, column: 1},
  70. lineShift: 0
  71. })
  72. if (
  73. result &&
  74. result.charCodeAt(result.length - 1) !== 10 &&
  75. result.charCodeAt(result.length - 1) !== 13
  76. ) {
  77. result += '\n'
  78. }
  79. return result
  80. /** @type {Enter} */
  81. function enter(name) {
  82. state.stack.push(name)
  83. return exit
  84. /**
  85. * @returns {undefined}
  86. */
  87. function exit() {
  88. state.stack.pop()
  89. }
  90. }
  91. }
  92. /**
  93. * @param {unknown} value
  94. * @returns {never}
  95. */
  96. function invalid(value) {
  97. throw new Error('Cannot handle value `' + value + '`, expected node')
  98. }
  99. /**
  100. * @param {unknown} value
  101. * @returns {never}
  102. */
  103. function unknown(value) {
  104. // Always a node.
  105. const node = /** @type {Nodes} */ (value)
  106. throw new Error('Cannot handle unknown node `' + node.type + '`')
  107. }
  108. /** @type {Join} */
  109. function joinDefinition(left, right) {
  110. // No blank line between adjacent definitions.
  111. if (left.type === 'definition' && left.type === right.type) {
  112. return 0
  113. }
  114. }
  115. /**
  116. * Serialize the children of a parent that contains phrasing children.
  117. *
  118. * These children will be joined flush together.
  119. *
  120. * @this {State}
  121. * Info passed around about the current state.
  122. * @param {PhrasingParents} parent
  123. * Parent of flow nodes.
  124. * @param {Info} info
  125. * Info on where we are in the document we are generating.
  126. * @returns {string}
  127. * Serialized children, joined together.
  128. */
  129. function containerPhrasingBound(parent, info) {
  130. return containerPhrasing(parent, this, info)
  131. }
  132. /**
  133. * Serialize the children of a parent that contains flow children.
  134. *
  135. * These children will typically be joined by blank lines.
  136. * What they are joined by exactly is defined by `Join` functions.
  137. *
  138. * @this {State}
  139. * Info passed around about the current state.
  140. * @param {FlowParents} parent
  141. * Parent of flow nodes.
  142. * @param {TrackFields} info
  143. * Info on where we are in the document we are generating.
  144. * @returns {string}
  145. * Serialized children, joined by (blank) lines.
  146. */
  147. function containerFlowBound(parent, info) {
  148. return containerFlow(parent, this, info)
  149. }
  150. /**
  151. * Make a string safe for embedding in markdown constructs.
  152. *
  153. * In markdown, almost all punctuation characters can, in certain cases,
  154. * result in something.
  155. * Whether they do is highly subjective to where they happen and in what
  156. * they happen.
  157. *
  158. * To solve this, `mdast-util-to-markdown` tracks:
  159. *
  160. * * Characters before and after something;
  161. * * What “constructs” we are in.
  162. *
  163. * This information is then used by this function to escape or encode
  164. * special characters.
  165. *
  166. * @this {State}
  167. * Info passed around about the current state.
  168. * @param {string | null | undefined} value
  169. * Raw value to make safe.
  170. * @param {SafeConfig} config
  171. * Configuration.
  172. * @returns {string}
  173. * Serialized markdown safe for embedding.
  174. */
  175. function safeBound(value, config) {
  176. return safe(this, value, config)
  177. }