index.js 3.8 KB

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