index.js 3.6 KB

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