index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /**
  2. * @typedef {import('micromark-util-types').Effects} Effects
  3. * @typedef {import('micromark-util-types').State} State
  4. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  5. * @typedef {import('micromark-util-types').TokenType} TokenType
  6. */
  7. import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
  8. /**
  9. * Parse labels.
  10. *
  11. * > 👉 **Note**: labels in markdown are capped at 999 characters in the string.
  12. *
  13. * ###### Examples
  14. *
  15. * ```markdown
  16. * [a]
  17. * [a
  18. * b]
  19. * [a\]b]
  20. * ```
  21. *
  22. * @this {TokenizeContext}
  23. * Tokenize context.
  24. * @param {Effects} effects
  25. * Context.
  26. * @param {State} ok
  27. * State switched to when successful.
  28. * @param {State} nok
  29. * State switched to when unsuccessful.
  30. * @param {TokenType} type
  31. * Type of the whole label (`[a]`).
  32. * @param {TokenType} markerType
  33. * Type for the markers (`[` and `]`).
  34. * @param {TokenType} stringType
  35. * Type for the identifier (`a`).
  36. * @returns {State}
  37. * Start state.
  38. */ // eslint-disable-next-line max-params
  39. export function factoryLabel(effects, ok, nok, type, markerType, stringType) {
  40. const self = this
  41. let size = 0
  42. /** @type {boolean} */
  43. let seen
  44. return start
  45. /**
  46. * Start of label.
  47. *
  48. * ```markdown
  49. * > | [a]
  50. * ^
  51. * ```
  52. *
  53. * @type {State}
  54. */
  55. function start(code) {
  56. effects.enter(type)
  57. effects.enter(markerType)
  58. effects.consume(code)
  59. effects.exit(markerType)
  60. effects.enter(stringType)
  61. return atBreak
  62. }
  63. /**
  64. * In label, at something, before something else.
  65. *
  66. * ```markdown
  67. * > | [a]
  68. * ^
  69. * ```
  70. *
  71. * @type {State}
  72. */
  73. function atBreak(code) {
  74. if (
  75. size > 999 ||
  76. code === null ||
  77. code === 91 ||
  78. (code === 93 && !seen) ||
  79. // To do: remove in the future once we’ve switched from
  80. // `micromark-extension-footnote` to `micromark-extension-gfm-footnote`,
  81. // which doesn’t need this.
  82. // Hidden footnotes hook.
  83. /* c8 ignore next 3 */
  84. (code === 94 &&
  85. !size &&
  86. '_hiddenFootnoteSupport' in self.parser.constructs)
  87. ) {
  88. return nok(code)
  89. }
  90. if (code === 93) {
  91. effects.exit(stringType)
  92. effects.enter(markerType)
  93. effects.consume(code)
  94. effects.exit(markerType)
  95. effects.exit(type)
  96. return ok
  97. }
  98. // To do: indent? Link chunks and EOLs together?
  99. if (markdownLineEnding(code)) {
  100. effects.enter('lineEnding')
  101. effects.consume(code)
  102. effects.exit('lineEnding')
  103. return atBreak
  104. }
  105. effects.enter('chunkString', {
  106. contentType: 'string'
  107. })
  108. return labelInside(code)
  109. }
  110. /**
  111. * In label, in text.
  112. *
  113. * ```markdown
  114. * > | [a]
  115. * ^
  116. * ```
  117. *
  118. * @type {State}
  119. */
  120. function labelInside(code) {
  121. if (
  122. code === null ||
  123. code === 91 ||
  124. code === 93 ||
  125. markdownLineEnding(code) ||
  126. size++ > 999
  127. ) {
  128. effects.exit('chunkString')
  129. return atBreak(code)
  130. }
  131. effects.consume(code)
  132. if (!seen) seen = !markdownSpace(code)
  133. return code === 92 ? labelEscape : labelInside
  134. }
  135. /**
  136. * After `\`, at a special character.
  137. *
  138. * ```markdown
  139. * > | [a\*a]
  140. * ^
  141. * ```
  142. *
  143. * @type {State}
  144. */
  145. function labelEscape(code) {
  146. if (code === 91 || code === 92 || code === 93) {
  147. effects.consume(code)
  148. size++
  149. return labelInside
  150. }
  151. return labelInside(code)
  152. }
  153. }