setext-underline.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /**
  2. * @typedef {import('micromark-util-types').Code} Code
  3. * @typedef {import('micromark-util-types').Construct} Construct
  4. * @typedef {import('micromark-util-types').Resolver} Resolver
  5. * @typedef {import('micromark-util-types').State} State
  6. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  7. * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
  8. */
  9. import {factorySpace} from 'micromark-factory-space'
  10. import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
  11. import {codes, types} from 'micromark-util-symbol'
  12. import {ok as assert} from 'devlop'
  13. /** @type {Construct} */
  14. export const setextUnderline = {
  15. name: 'setextUnderline',
  16. tokenize: tokenizeSetextUnderline,
  17. resolveTo: resolveToSetextUnderline
  18. }
  19. /** @type {Resolver} */
  20. function resolveToSetextUnderline(events, context) {
  21. // To do: resolve like `markdown-rs`.
  22. let index = events.length
  23. /** @type {number | undefined} */
  24. let content
  25. /** @type {number | undefined} */
  26. let text
  27. /** @type {number | undefined} */
  28. let definition
  29. // Find the opening of the content.
  30. // It’ll always exist: we don’t tokenize if it isn’t there.
  31. while (index--) {
  32. if (events[index][0] === 'enter') {
  33. if (events[index][1].type === types.content) {
  34. content = index
  35. break
  36. }
  37. if (events[index][1].type === types.paragraph) {
  38. text = index
  39. }
  40. }
  41. // Exit
  42. else {
  43. if (events[index][1].type === types.content) {
  44. // Remove the content end (if needed we’ll add it later)
  45. events.splice(index, 1)
  46. }
  47. if (!definition && events[index][1].type === types.definition) {
  48. definition = index
  49. }
  50. }
  51. }
  52. assert(text !== undefined, 'expected a `text` index to be found')
  53. assert(content !== undefined, 'expected a `text` index to be found')
  54. const heading = {
  55. type: types.setextHeading,
  56. start: Object.assign({}, events[text][1].start),
  57. end: Object.assign({}, events[events.length - 1][1].end)
  58. }
  59. // Change the paragraph to setext heading text.
  60. events[text][1].type = types.setextHeadingText
  61. // If we have definitions in the content, we’ll keep on having content,
  62. // but we need move it.
  63. if (definition) {
  64. events.splice(text, 0, ['enter', heading, context])
  65. events.splice(definition + 1, 0, ['exit', events[content][1], context])
  66. events[content][1].end = Object.assign({}, events[definition][1].end)
  67. } else {
  68. events[content][1] = heading
  69. }
  70. // Add the heading exit at the end.
  71. events.push(['exit', heading, context])
  72. return events
  73. }
  74. /**
  75. * @this {TokenizeContext}
  76. * @type {Tokenizer}
  77. */
  78. function tokenizeSetextUnderline(effects, ok, nok) {
  79. const self = this
  80. /** @type {NonNullable<Code>} */
  81. let marker
  82. return start
  83. /**
  84. * At start of heading (setext) underline.
  85. *
  86. * ```markdown
  87. * | aa
  88. * > | ==
  89. * ^
  90. * ```
  91. *
  92. * @type {State}
  93. */
  94. function start(code) {
  95. let index = self.events.length
  96. /** @type {boolean | undefined} */
  97. let paragraph
  98. assert(
  99. code === codes.dash || code === codes.equalsTo,
  100. 'expected `=` or `-`'
  101. )
  102. // Find an opening.
  103. while (index--) {
  104. // Skip enter/exit of line ending, line prefix, and content.
  105. // We can now either have a definition or a paragraph.
  106. if (
  107. self.events[index][1].type !== types.lineEnding &&
  108. self.events[index][1].type !== types.linePrefix &&
  109. self.events[index][1].type !== types.content
  110. ) {
  111. paragraph = self.events[index][1].type === types.paragraph
  112. break
  113. }
  114. }
  115. // To do: handle lazy/pierce like `markdown-rs`.
  116. // To do: parse indent like `markdown-rs`.
  117. if (!self.parser.lazy[self.now().line] && (self.interrupt || paragraph)) {
  118. effects.enter(types.setextHeadingLine)
  119. marker = code
  120. return before(code)
  121. }
  122. return nok(code)
  123. }
  124. /**
  125. * After optional whitespace, at `-` or `=`.
  126. *
  127. * ```markdown
  128. * | aa
  129. * > | ==
  130. * ^
  131. * ```
  132. *
  133. * @type {State}
  134. */
  135. function before(code) {
  136. effects.enter(types.setextHeadingLineSequence)
  137. return inside(code)
  138. }
  139. /**
  140. * In sequence.
  141. *
  142. * ```markdown
  143. * | aa
  144. * > | ==
  145. * ^
  146. * ```
  147. *
  148. * @type {State}
  149. */
  150. function inside(code) {
  151. if (code === marker) {
  152. effects.consume(code)
  153. return inside
  154. }
  155. effects.exit(types.setextHeadingLineSequence)
  156. return markdownSpace(code)
  157. ? factorySpace(effects, after, types.lineSuffix)(code)
  158. : after(code)
  159. }
  160. /**
  161. * After sequence, after optional whitespace.
  162. *
  163. * ```markdown
  164. * | aa
  165. * > | ==
  166. * ^
  167. * ```
  168. *
  169. * @type {State}
  170. */
  171. function after(code) {
  172. if (code === codes.eof || markdownLineEnding(code)) {
  173. effects.exit(types.setextHeadingLine)
  174. return ok(code)
  175. }
  176. return nok(code)
  177. }
  178. }