autolink.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /**
  2. * @import {
  3. * Construct,
  4. * State,
  5. * TokenizeContext,
  6. * Tokenizer
  7. * } from 'micromark-util-types'
  8. */
  9. import { asciiAlphanumeric, asciiAlpha, asciiAtext, asciiControl } from 'micromark-util-character';
  10. /** @type {Construct} */
  11. export const autolink = {
  12. name: 'autolink',
  13. tokenize: tokenizeAutolink
  14. };
  15. /**
  16. * @this {TokenizeContext}
  17. * Context.
  18. * @type {Tokenizer}
  19. */
  20. function tokenizeAutolink(effects, ok, nok) {
  21. let size = 0;
  22. return start;
  23. /**
  24. * Start of an autolink.
  25. *
  26. * ```markdown
  27. * > | a<https://example.com>b
  28. * ^
  29. * > | a<user@example.com>b
  30. * ^
  31. * ```
  32. *
  33. * @type {State}
  34. */
  35. function start(code) {
  36. effects.enter("autolink");
  37. effects.enter("autolinkMarker");
  38. effects.consume(code);
  39. effects.exit("autolinkMarker");
  40. effects.enter("autolinkProtocol");
  41. return open;
  42. }
  43. /**
  44. * After `<`, at protocol or atext.
  45. *
  46. * ```markdown
  47. * > | a<https://example.com>b
  48. * ^
  49. * > | a<user@example.com>b
  50. * ^
  51. * ```
  52. *
  53. * @type {State}
  54. */
  55. function open(code) {
  56. if (asciiAlpha(code)) {
  57. effects.consume(code);
  58. return schemeOrEmailAtext;
  59. }
  60. if (code === 64) {
  61. return nok(code);
  62. }
  63. return emailAtext(code);
  64. }
  65. /**
  66. * At second byte of protocol or atext.
  67. *
  68. * ```markdown
  69. * > | a<https://example.com>b
  70. * ^
  71. * > | a<user@example.com>b
  72. * ^
  73. * ```
  74. *
  75. * @type {State}
  76. */
  77. function schemeOrEmailAtext(code) {
  78. // ASCII alphanumeric and `+`, `-`, and `.`.
  79. if (code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) {
  80. // Count the previous alphabetical from `open` too.
  81. size = 1;
  82. return schemeInsideOrEmailAtext(code);
  83. }
  84. return emailAtext(code);
  85. }
  86. /**
  87. * In ambiguous protocol or atext.
  88. *
  89. * ```markdown
  90. * > | a<https://example.com>b
  91. * ^
  92. * > | a<user@example.com>b
  93. * ^
  94. * ```
  95. *
  96. * @type {State}
  97. */
  98. function schemeInsideOrEmailAtext(code) {
  99. if (code === 58) {
  100. effects.consume(code);
  101. size = 0;
  102. return urlInside;
  103. }
  104. // ASCII alphanumeric and `+`, `-`, and `.`.
  105. if ((code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) && size++ < 32) {
  106. effects.consume(code);
  107. return schemeInsideOrEmailAtext;
  108. }
  109. size = 0;
  110. return emailAtext(code);
  111. }
  112. /**
  113. * After protocol, in URL.
  114. *
  115. * ```markdown
  116. * > | a<https://example.com>b
  117. * ^
  118. * ```
  119. *
  120. * @type {State}
  121. */
  122. function urlInside(code) {
  123. if (code === 62) {
  124. effects.exit("autolinkProtocol");
  125. effects.enter("autolinkMarker");
  126. effects.consume(code);
  127. effects.exit("autolinkMarker");
  128. effects.exit("autolink");
  129. return ok;
  130. }
  131. // ASCII control, space, or `<`.
  132. if (code === null || code === 32 || code === 60 || asciiControl(code)) {
  133. return nok(code);
  134. }
  135. effects.consume(code);
  136. return urlInside;
  137. }
  138. /**
  139. * In email atext.
  140. *
  141. * ```markdown
  142. * > | a<user.name@example.com>b
  143. * ^
  144. * ```
  145. *
  146. * @type {State}
  147. */
  148. function emailAtext(code) {
  149. if (code === 64) {
  150. effects.consume(code);
  151. return emailAtSignOrDot;
  152. }
  153. if (asciiAtext(code)) {
  154. effects.consume(code);
  155. return emailAtext;
  156. }
  157. return nok(code);
  158. }
  159. /**
  160. * In label, after at-sign or dot.
  161. *
  162. * ```markdown
  163. * > | a<user.name@example.com>b
  164. * ^ ^
  165. * ```
  166. *
  167. * @type {State}
  168. */
  169. function emailAtSignOrDot(code) {
  170. return asciiAlphanumeric(code) ? emailLabel(code) : nok(code);
  171. }
  172. /**
  173. * In label, where `.` and `>` are allowed.
  174. *
  175. * ```markdown
  176. * > | a<user.name@example.com>b
  177. * ^
  178. * ```
  179. *
  180. * @type {State}
  181. */
  182. function emailLabel(code) {
  183. if (code === 46) {
  184. effects.consume(code);
  185. size = 0;
  186. return emailAtSignOrDot;
  187. }
  188. if (code === 62) {
  189. // Exit, then change the token type.
  190. effects.exit("autolinkProtocol").type = "autolinkEmail";
  191. effects.enter("autolinkMarker");
  192. effects.consume(code);
  193. effects.exit("autolinkMarker");
  194. effects.exit("autolink");
  195. return ok;
  196. }
  197. return emailValue(code);
  198. }
  199. /**
  200. * In label, where `.` and `>` are *not* allowed.
  201. *
  202. * Though, this is also used in `emailLabel` to parse other values.
  203. *
  204. * ```markdown
  205. * > | a<user.name@ex-ample.com>b
  206. * ^
  207. * ```
  208. *
  209. * @type {State}
  210. */
  211. function emailValue(code) {
  212. // ASCII alphanumeric or `-`.
  213. if ((code === 45 || asciiAlphanumeric(code)) && size++ < 63) {
  214. const next = code === 45 ? emailValue : emailLabel;
  215. effects.consume(code);
  216. return next;
  217. }
  218. return nok(code);
  219. }
  220. }