index.js 4.7 KB

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