index.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * @import {
  3. * Effects,
  4. * State,
  5. * TokenizeContext,
  6. * TokenType
  7. * } from 'micromark-util-types'
  8. */
  9. import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
  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. export function factoryLabel(effects, ok, nok, type, markerType, stringType) {
  42. const self = this;
  43. let size = 0;
  44. /** @type {boolean} */
  45. let seen;
  46. return start;
  47. /**
  48. * Start of label.
  49. *
  50. * ```markdown
  51. * > | [a]
  52. * ^
  53. * ```
  54. *
  55. * @type {State}
  56. */
  57. function start(code) {
  58. effects.enter(type);
  59. effects.enter(markerType);
  60. effects.consume(code);
  61. effects.exit(markerType);
  62. effects.enter(stringType);
  63. return atBreak;
  64. }
  65. /**
  66. * In label, at something, before something else.
  67. *
  68. * ```markdown
  69. * > | [a]
  70. * ^
  71. * ```
  72. *
  73. * @type {State}
  74. */
  75. function atBreak(code) {
  76. if (size > 999 || code === null || code === 91 || code === 93 && !seen ||
  77. // To do: remove in the future once we’ve switched from
  78. // `micromark-extension-footnote` to `micromark-extension-gfm-footnote`,
  79. // which doesn’t need this.
  80. // Hidden footnotes hook.
  81. /* c8 ignore next 3 */
  82. code === 94 && !size && '_hiddenFootnoteSupport' in self.parser.constructs) {
  83. return nok(code);
  84. }
  85. if (code === 93) {
  86. effects.exit(stringType);
  87. effects.enter(markerType);
  88. effects.consume(code);
  89. effects.exit(markerType);
  90. effects.exit(type);
  91. return ok;
  92. }
  93. // To do: indent? Link chunks and EOLs together?
  94. if (markdownLineEnding(code)) {
  95. effects.enter("lineEnding");
  96. effects.consume(code);
  97. effects.exit("lineEnding");
  98. return atBreak;
  99. }
  100. effects.enter("chunkString", {
  101. contentType: "string"
  102. });
  103. return labelInside(code);
  104. }
  105. /**
  106. * In label, in text.
  107. *
  108. * ```markdown
  109. * > | [a]
  110. * ^
  111. * ```
  112. *
  113. * @type {State}
  114. */
  115. function labelInside(code) {
  116. if (code === null || code === 91 || code === 93 || markdownLineEnding(code) || size++ > 999) {
  117. effects.exit("chunkString");
  118. return atBreak(code);
  119. }
  120. effects.consume(code);
  121. if (!seen) seen = !markdownSpace(code);
  122. return code === 92 ? labelEscape : labelInside;
  123. }
  124. /**
  125. * After `\`, at a special character.
  126. *
  127. * ```markdown
  128. * > | [a\*a]
  129. * ^
  130. * ```
  131. *
  132. * @type {State}
  133. */
  134. function labelEscape(code) {
  135. if (code === 91 || code === 92 || code === 93) {
  136. effects.consume(code);
  137. size++;
  138. return labelInside;
  139. }
  140. return labelInside(code);
  141. }
  142. }