code-fenced.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /**
  2. * @import {
  3. * Code,
  4. * Construct,
  5. * State,
  6. * TokenizeContext,
  7. * Tokenizer
  8. * } from 'micromark-util-types'
  9. */
  10. import { factorySpace } from 'micromark-factory-space';
  11. import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
  12. /** @type {Construct} */
  13. const nonLazyContinuation = {
  14. partial: true,
  15. tokenize: tokenizeNonLazyContinuation
  16. };
  17. /** @type {Construct} */
  18. export const codeFenced = {
  19. concrete: true,
  20. name: 'codeFenced',
  21. tokenize: tokenizeCodeFenced
  22. };
  23. /**
  24. * @this {TokenizeContext}
  25. * Context.
  26. * @type {Tokenizer}
  27. */
  28. function tokenizeCodeFenced(effects, ok, nok) {
  29. const self = this;
  30. /** @type {Construct} */
  31. const closeStart = {
  32. partial: true,
  33. tokenize: tokenizeCloseStart
  34. };
  35. let initialPrefix = 0;
  36. let sizeOpen = 0;
  37. /** @type {NonNullable<Code>} */
  38. let marker;
  39. return start;
  40. /**
  41. * Start of code.
  42. *
  43. * ```markdown
  44. * > | ~~~js
  45. * ^
  46. * | alert(1)
  47. * | ~~~
  48. * ```
  49. *
  50. * @type {State}
  51. */
  52. function start(code) {
  53. // To do: parse whitespace like `markdown-rs`.
  54. return beforeSequenceOpen(code);
  55. }
  56. /**
  57. * In opening fence, after prefix, at sequence.
  58. *
  59. * ```markdown
  60. * > | ~~~js
  61. * ^
  62. * | alert(1)
  63. * | ~~~
  64. * ```
  65. *
  66. * @type {State}
  67. */
  68. function beforeSequenceOpen(code) {
  69. const tail = self.events[self.events.length - 1];
  70. initialPrefix = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
  71. marker = code;
  72. effects.enter("codeFenced");
  73. effects.enter("codeFencedFence");
  74. effects.enter("codeFencedFenceSequence");
  75. return sequenceOpen(code);
  76. }
  77. /**
  78. * In opening fence sequence.
  79. *
  80. * ```markdown
  81. * > | ~~~js
  82. * ^
  83. * | alert(1)
  84. * | ~~~
  85. * ```
  86. *
  87. * @type {State}
  88. */
  89. function sequenceOpen(code) {
  90. if (code === marker) {
  91. sizeOpen++;
  92. effects.consume(code);
  93. return sequenceOpen;
  94. }
  95. if (sizeOpen < 3) {
  96. return nok(code);
  97. }
  98. effects.exit("codeFencedFenceSequence");
  99. return markdownSpace(code) ? factorySpace(effects, infoBefore, "whitespace")(code) : infoBefore(code);
  100. }
  101. /**
  102. * In opening fence, after the sequence (and optional whitespace), before info.
  103. *
  104. * ```markdown
  105. * > | ~~~js
  106. * ^
  107. * | alert(1)
  108. * | ~~~
  109. * ```
  110. *
  111. * @type {State}
  112. */
  113. function infoBefore(code) {
  114. if (code === null || markdownLineEnding(code)) {
  115. effects.exit("codeFencedFence");
  116. return self.interrupt ? ok(code) : effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
  117. }
  118. effects.enter("codeFencedFenceInfo");
  119. effects.enter("chunkString", {
  120. contentType: "string"
  121. });
  122. return info(code);
  123. }
  124. /**
  125. * In info.
  126. *
  127. * ```markdown
  128. * > | ~~~js
  129. * ^
  130. * | alert(1)
  131. * | ~~~
  132. * ```
  133. *
  134. * @type {State}
  135. */
  136. function info(code) {
  137. if (code === null || markdownLineEnding(code)) {
  138. effects.exit("chunkString");
  139. effects.exit("codeFencedFenceInfo");
  140. return infoBefore(code);
  141. }
  142. if (markdownSpace(code)) {
  143. effects.exit("chunkString");
  144. effects.exit("codeFencedFenceInfo");
  145. return factorySpace(effects, metaBefore, "whitespace")(code);
  146. }
  147. if (code === 96 && code === marker) {
  148. return nok(code);
  149. }
  150. effects.consume(code);
  151. return info;
  152. }
  153. /**
  154. * In opening fence, after info and whitespace, before meta.
  155. *
  156. * ```markdown
  157. * > | ~~~js eval
  158. * ^
  159. * | alert(1)
  160. * | ~~~
  161. * ```
  162. *
  163. * @type {State}
  164. */
  165. function metaBefore(code) {
  166. if (code === null || markdownLineEnding(code)) {
  167. return infoBefore(code);
  168. }
  169. effects.enter("codeFencedFenceMeta");
  170. effects.enter("chunkString", {
  171. contentType: "string"
  172. });
  173. return meta(code);
  174. }
  175. /**
  176. * In meta.
  177. *
  178. * ```markdown
  179. * > | ~~~js eval
  180. * ^
  181. * | alert(1)
  182. * | ~~~
  183. * ```
  184. *
  185. * @type {State}
  186. */
  187. function meta(code) {
  188. if (code === null || markdownLineEnding(code)) {
  189. effects.exit("chunkString");
  190. effects.exit("codeFencedFenceMeta");
  191. return infoBefore(code);
  192. }
  193. if (code === 96 && code === marker) {
  194. return nok(code);
  195. }
  196. effects.consume(code);
  197. return meta;
  198. }
  199. /**
  200. * At eol/eof in code, before a non-lazy closing fence or content.
  201. *
  202. * ```markdown
  203. * > | ~~~js
  204. * ^
  205. * > | alert(1)
  206. * ^
  207. * | ~~~
  208. * ```
  209. *
  210. * @type {State}
  211. */
  212. function atNonLazyBreak(code) {
  213. return effects.attempt(closeStart, after, contentBefore)(code);
  214. }
  215. /**
  216. * Before code content, not a closing fence, at eol.
  217. *
  218. * ```markdown
  219. * | ~~~js
  220. * > | alert(1)
  221. * ^
  222. * | ~~~
  223. * ```
  224. *
  225. * @type {State}
  226. */
  227. function contentBefore(code) {
  228. effects.enter("lineEnding");
  229. effects.consume(code);
  230. effects.exit("lineEnding");
  231. return contentStart;
  232. }
  233. /**
  234. * Before code content, not a closing fence.
  235. *
  236. * ```markdown
  237. * | ~~~js
  238. * > | alert(1)
  239. * ^
  240. * | ~~~
  241. * ```
  242. *
  243. * @type {State}
  244. */
  245. function contentStart(code) {
  246. return initialPrefix > 0 && markdownSpace(code) ? factorySpace(effects, beforeContentChunk, "linePrefix", initialPrefix + 1)(code) : beforeContentChunk(code);
  247. }
  248. /**
  249. * Before code content, after optional prefix.
  250. *
  251. * ```markdown
  252. * | ~~~js
  253. * > | alert(1)
  254. * ^
  255. * | ~~~
  256. * ```
  257. *
  258. * @type {State}
  259. */
  260. function beforeContentChunk(code) {
  261. if (code === null || markdownLineEnding(code)) {
  262. return effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
  263. }
  264. effects.enter("codeFlowValue");
  265. return contentChunk(code);
  266. }
  267. /**
  268. * In code content.
  269. *
  270. * ```markdown
  271. * | ~~~js
  272. * > | alert(1)
  273. * ^^^^^^^^
  274. * | ~~~
  275. * ```
  276. *
  277. * @type {State}
  278. */
  279. function contentChunk(code) {
  280. if (code === null || markdownLineEnding(code)) {
  281. effects.exit("codeFlowValue");
  282. return beforeContentChunk(code);
  283. }
  284. effects.consume(code);
  285. return contentChunk;
  286. }
  287. /**
  288. * After code.
  289. *
  290. * ```markdown
  291. * | ~~~js
  292. * | alert(1)
  293. * > | ~~~
  294. * ^
  295. * ```
  296. *
  297. * @type {State}
  298. */
  299. function after(code) {
  300. effects.exit("codeFenced");
  301. return ok(code);
  302. }
  303. /**
  304. * @this {TokenizeContext}
  305. * Context.
  306. * @type {Tokenizer}
  307. */
  308. function tokenizeCloseStart(effects, ok, nok) {
  309. let size = 0;
  310. return startBefore;
  311. /**
  312. *
  313. *
  314. * @type {State}
  315. */
  316. function startBefore(code) {
  317. effects.enter("lineEnding");
  318. effects.consume(code);
  319. effects.exit("lineEnding");
  320. return start;
  321. }
  322. /**
  323. * Before closing fence, at optional whitespace.
  324. *
  325. * ```markdown
  326. * | ~~~js
  327. * | alert(1)
  328. * > | ~~~
  329. * ^
  330. * ```
  331. *
  332. * @type {State}
  333. */
  334. function start(code) {
  335. // Always populated by defaults.
  336. // To do: `enter` here or in next state?
  337. effects.enter("codeFencedFence");
  338. return markdownSpace(code) ? factorySpace(effects, beforeSequenceClose, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : beforeSequenceClose(code);
  339. }
  340. /**
  341. * In closing fence, after optional whitespace, at sequence.
  342. *
  343. * ```markdown
  344. * | ~~~js
  345. * | alert(1)
  346. * > | ~~~
  347. * ^
  348. * ```
  349. *
  350. * @type {State}
  351. */
  352. function beforeSequenceClose(code) {
  353. if (code === marker) {
  354. effects.enter("codeFencedFenceSequence");
  355. return sequenceClose(code);
  356. }
  357. return nok(code);
  358. }
  359. /**
  360. * In closing fence sequence.
  361. *
  362. * ```markdown
  363. * | ~~~js
  364. * | alert(1)
  365. * > | ~~~
  366. * ^
  367. * ```
  368. *
  369. * @type {State}
  370. */
  371. function sequenceClose(code) {
  372. if (code === marker) {
  373. size++;
  374. effects.consume(code);
  375. return sequenceClose;
  376. }
  377. if (size >= sizeOpen) {
  378. effects.exit("codeFencedFenceSequence");
  379. return markdownSpace(code) ? factorySpace(effects, sequenceCloseAfter, "whitespace")(code) : sequenceCloseAfter(code);
  380. }
  381. return nok(code);
  382. }
  383. /**
  384. * After closing fence sequence, after optional whitespace.
  385. *
  386. * ```markdown
  387. * | ~~~js
  388. * | alert(1)
  389. * > | ~~~
  390. * ^
  391. * ```
  392. *
  393. * @type {State}
  394. */
  395. function sequenceCloseAfter(code) {
  396. if (code === null || markdownLineEnding(code)) {
  397. effects.exit("codeFencedFence");
  398. return ok(code);
  399. }
  400. return nok(code);
  401. }
  402. }
  403. }
  404. /**
  405. * @this {TokenizeContext}
  406. * Context.
  407. * @type {Tokenizer}
  408. */
  409. function tokenizeNonLazyContinuation(effects, ok, nok) {
  410. const self = this;
  411. return start;
  412. /**
  413. *
  414. *
  415. * @type {State}
  416. */
  417. function start(code) {
  418. if (code === null) {
  419. return nok(code);
  420. }
  421. effects.enter("lineEnding");
  422. effects.consume(code);
  423. effects.exit("lineEnding");
  424. return lineStart;
  425. }
  426. /**
  427. *
  428. *
  429. * @type {State}
  430. */
  431. function lineStart(code) {
  432. return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  433. }
  434. }