html-flow.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. /**
  2. * @typedef {import('micromark-util-types').Code} Code
  3. * @typedef {import('micromark-util-types').Construct} Construct
  4. * @typedef {import('micromark-util-types').Resolver} Resolver
  5. * @typedef {import('micromark-util-types').State} State
  6. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  7. * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
  8. */
  9. import {
  10. asciiAlpha,
  11. asciiAlphanumeric,
  12. markdownLineEnding,
  13. markdownLineEndingOrSpace,
  14. markdownSpace
  15. } from 'micromark-util-character'
  16. import {htmlBlockNames, htmlRawNames} from 'micromark-util-html-tag-name'
  17. import {codes, constants, types} from 'micromark-util-symbol'
  18. import {ok as assert} from 'devlop'
  19. import {blankLine} from './blank-line.js'
  20. /** @type {Construct} */
  21. export const htmlFlow = {
  22. name: 'htmlFlow',
  23. tokenize: tokenizeHtmlFlow,
  24. resolveTo: resolveToHtmlFlow,
  25. concrete: true
  26. }
  27. /** @type {Construct} */
  28. const blankLineBefore = {tokenize: tokenizeBlankLineBefore, partial: true}
  29. const nonLazyContinuationStart = {
  30. tokenize: tokenizeNonLazyContinuationStart,
  31. partial: true
  32. }
  33. /** @type {Resolver} */
  34. function resolveToHtmlFlow(events) {
  35. let index = events.length
  36. while (index--) {
  37. if (
  38. events[index][0] === 'enter' &&
  39. events[index][1].type === types.htmlFlow
  40. ) {
  41. break
  42. }
  43. }
  44. if (index > 1 && events[index - 2][1].type === types.linePrefix) {
  45. // Add the prefix start to the HTML token.
  46. events[index][1].start = events[index - 2][1].start
  47. // Add the prefix start to the HTML line token.
  48. events[index + 1][1].start = events[index - 2][1].start
  49. // Remove the line prefix.
  50. events.splice(index - 2, 2)
  51. }
  52. return events
  53. }
  54. /**
  55. * @this {TokenizeContext}
  56. * @type {Tokenizer}
  57. */
  58. function tokenizeHtmlFlow(effects, ok, nok) {
  59. const self = this
  60. /** @type {number} */
  61. let marker
  62. /** @type {boolean} */
  63. let closingTag
  64. /** @type {string} */
  65. let buffer
  66. /** @type {number} */
  67. let index
  68. /** @type {Code} */
  69. let markerB
  70. return start
  71. /**
  72. * Start of HTML (flow).
  73. *
  74. * ```markdown
  75. * > | <x />
  76. * ^
  77. * ```
  78. *
  79. * @type {State}
  80. */
  81. function start(code) {
  82. // To do: parse indent like `markdown-rs`.
  83. return before(code)
  84. }
  85. /**
  86. * At `<`, after optional whitespace.
  87. *
  88. * ```markdown
  89. * > | <x />
  90. * ^
  91. * ```
  92. *
  93. * @type {State}
  94. */
  95. function before(code) {
  96. assert(code === codes.lessThan, 'expected `<`')
  97. effects.enter(types.htmlFlow)
  98. effects.enter(types.htmlFlowData)
  99. effects.consume(code)
  100. return open
  101. }
  102. /**
  103. * After `<`, at tag name or other stuff.
  104. *
  105. * ```markdown
  106. * > | <x />
  107. * ^
  108. * > | <!doctype>
  109. * ^
  110. * > | <!--xxx-->
  111. * ^
  112. * ```
  113. *
  114. * @type {State}
  115. */
  116. function open(code) {
  117. if (code === codes.exclamationMark) {
  118. effects.consume(code)
  119. return declarationOpen
  120. }
  121. if (code === codes.slash) {
  122. effects.consume(code)
  123. closingTag = true
  124. return tagCloseStart
  125. }
  126. if (code === codes.questionMark) {
  127. effects.consume(code)
  128. marker = constants.htmlInstruction
  129. // To do:
  130. // tokenizer.concrete = true
  131. // To do: use `markdown-rs` style interrupt.
  132. // While we’re in an instruction instead of a declaration, we’re on a `?`
  133. // right now, so we do need to search for `>`, similar to declarations.
  134. return self.interrupt ? ok : continuationDeclarationInside
  135. }
  136. // ASCII alphabetical.
  137. if (asciiAlpha(code)) {
  138. effects.consume(code)
  139. // @ts-expect-error: not null.
  140. buffer = String.fromCharCode(code)
  141. return tagName
  142. }
  143. return nok(code)
  144. }
  145. /**
  146. * After `<!`, at declaration, comment, or CDATA.
  147. *
  148. * ```markdown
  149. * > | <!doctype>
  150. * ^
  151. * > | <!--xxx-->
  152. * ^
  153. * > | <![CDATA[>&<]]>
  154. * ^
  155. * ```
  156. *
  157. * @type {State}
  158. */
  159. function declarationOpen(code) {
  160. if (code === codes.dash) {
  161. effects.consume(code)
  162. marker = constants.htmlComment
  163. return commentOpenInside
  164. }
  165. if (code === codes.leftSquareBracket) {
  166. effects.consume(code)
  167. marker = constants.htmlCdata
  168. index = 0
  169. return cdataOpenInside
  170. }
  171. // ASCII alphabetical.
  172. if (asciiAlpha(code)) {
  173. effects.consume(code)
  174. marker = constants.htmlDeclaration
  175. // // Do not form containers.
  176. // tokenizer.concrete = true
  177. return self.interrupt ? ok : continuationDeclarationInside
  178. }
  179. return nok(code)
  180. }
  181. /**
  182. * After `<!-`, inside a comment, at another `-`.
  183. *
  184. * ```markdown
  185. * > | <!--xxx-->
  186. * ^
  187. * ```
  188. *
  189. * @type {State}
  190. */
  191. function commentOpenInside(code) {
  192. if (code === codes.dash) {
  193. effects.consume(code)
  194. // // Do not form containers.
  195. // tokenizer.concrete = true
  196. return self.interrupt ? ok : continuationDeclarationInside
  197. }
  198. return nok(code)
  199. }
  200. /**
  201. * After `<![`, inside CDATA, expecting `CDATA[`.
  202. *
  203. * ```markdown
  204. * > | <![CDATA[>&<]]>
  205. * ^^^^^^
  206. * ```
  207. *
  208. * @type {State}
  209. */
  210. function cdataOpenInside(code) {
  211. const value = constants.cdataOpeningString
  212. if (code === value.charCodeAt(index++)) {
  213. effects.consume(code)
  214. if (index === value.length) {
  215. // // Do not form containers.
  216. // tokenizer.concrete = true
  217. return self.interrupt ? ok : continuation
  218. }
  219. return cdataOpenInside
  220. }
  221. return nok(code)
  222. }
  223. /**
  224. * After `</`, in closing tag, at tag name.
  225. *
  226. * ```markdown
  227. * > | </x>
  228. * ^
  229. * ```
  230. *
  231. * @type {State}
  232. */
  233. function tagCloseStart(code) {
  234. if (asciiAlpha(code)) {
  235. effects.consume(code)
  236. // @ts-expect-error: not null.
  237. buffer = String.fromCharCode(code)
  238. return tagName
  239. }
  240. return nok(code)
  241. }
  242. /**
  243. * In tag name.
  244. *
  245. * ```markdown
  246. * > | <ab>
  247. * ^^
  248. * > | </ab>
  249. * ^^
  250. * ```
  251. *
  252. * @type {State}
  253. */
  254. function tagName(code) {
  255. if (
  256. code === codes.eof ||
  257. code === codes.slash ||
  258. code === codes.greaterThan ||
  259. markdownLineEndingOrSpace(code)
  260. ) {
  261. const slash = code === codes.slash
  262. const name = buffer.toLowerCase()
  263. if (!slash && !closingTag && htmlRawNames.includes(name)) {
  264. marker = constants.htmlRaw
  265. // // Do not form containers.
  266. // tokenizer.concrete = true
  267. return self.interrupt ? ok(code) : continuation(code)
  268. }
  269. if (htmlBlockNames.includes(buffer.toLowerCase())) {
  270. marker = constants.htmlBasic
  271. if (slash) {
  272. effects.consume(code)
  273. return basicSelfClosing
  274. }
  275. // // Do not form containers.
  276. // tokenizer.concrete = true
  277. return self.interrupt ? ok(code) : continuation(code)
  278. }
  279. marker = constants.htmlComplete
  280. // Do not support complete HTML when interrupting.
  281. return self.interrupt && !self.parser.lazy[self.now().line]
  282. ? nok(code)
  283. : closingTag
  284. ? completeClosingTagAfter(code)
  285. : completeAttributeNameBefore(code)
  286. }
  287. // ASCII alphanumerical and `-`.
  288. if (code === codes.dash || asciiAlphanumeric(code)) {
  289. effects.consume(code)
  290. buffer += String.fromCharCode(code)
  291. return tagName
  292. }
  293. return nok(code)
  294. }
  295. /**
  296. * After closing slash of a basic tag name.
  297. *
  298. * ```markdown
  299. * > | <div/>
  300. * ^
  301. * ```
  302. *
  303. * @type {State}
  304. */
  305. function basicSelfClosing(code) {
  306. if (code === codes.greaterThan) {
  307. effects.consume(code)
  308. // // Do not form containers.
  309. // tokenizer.concrete = true
  310. return self.interrupt ? ok : continuation
  311. }
  312. return nok(code)
  313. }
  314. /**
  315. * After closing slash of a complete tag name.
  316. *
  317. * ```markdown
  318. * > | <x/>
  319. * ^
  320. * ```
  321. *
  322. * @type {State}
  323. */
  324. function completeClosingTagAfter(code) {
  325. if (markdownSpace(code)) {
  326. effects.consume(code)
  327. return completeClosingTagAfter
  328. }
  329. return completeEnd(code)
  330. }
  331. /**
  332. * At an attribute name.
  333. *
  334. * At first, this state is used after a complete tag name, after whitespace,
  335. * where it expects optional attributes or the end of the tag.
  336. * It is also reused after attributes, when expecting more optional
  337. * attributes.
  338. *
  339. * ```markdown
  340. * > | <a />
  341. * ^
  342. * > | <a :b>
  343. * ^
  344. * > | <a _b>
  345. * ^
  346. * > | <a b>
  347. * ^
  348. * > | <a >
  349. * ^
  350. * ```
  351. *
  352. * @type {State}
  353. */
  354. function completeAttributeNameBefore(code) {
  355. if (code === codes.slash) {
  356. effects.consume(code)
  357. return completeEnd
  358. }
  359. // ASCII alphanumerical and `:` and `_`.
  360. if (code === codes.colon || code === codes.underscore || asciiAlpha(code)) {
  361. effects.consume(code)
  362. return completeAttributeName
  363. }
  364. if (markdownSpace(code)) {
  365. effects.consume(code)
  366. return completeAttributeNameBefore
  367. }
  368. return completeEnd(code)
  369. }
  370. /**
  371. * In attribute name.
  372. *
  373. * ```markdown
  374. * > | <a :b>
  375. * ^
  376. * > | <a _b>
  377. * ^
  378. * > | <a b>
  379. * ^
  380. * ```
  381. *
  382. * @type {State}
  383. */
  384. function completeAttributeName(code) {
  385. // ASCII alphanumerical and `-`, `.`, `:`, and `_`.
  386. if (
  387. code === codes.dash ||
  388. code === codes.dot ||
  389. code === codes.colon ||
  390. code === codes.underscore ||
  391. asciiAlphanumeric(code)
  392. ) {
  393. effects.consume(code)
  394. return completeAttributeName
  395. }
  396. return completeAttributeNameAfter(code)
  397. }
  398. /**
  399. * After attribute name, at an optional initializer, the end of the tag, or
  400. * whitespace.
  401. *
  402. * ```markdown
  403. * > | <a b>
  404. * ^
  405. * > | <a b=c>
  406. * ^
  407. * ```
  408. *
  409. * @type {State}
  410. */
  411. function completeAttributeNameAfter(code) {
  412. if (code === codes.equalsTo) {
  413. effects.consume(code)
  414. return completeAttributeValueBefore
  415. }
  416. if (markdownSpace(code)) {
  417. effects.consume(code)
  418. return completeAttributeNameAfter
  419. }
  420. return completeAttributeNameBefore(code)
  421. }
  422. /**
  423. * Before unquoted, double quoted, or single quoted attribute value, allowing
  424. * whitespace.
  425. *
  426. * ```markdown
  427. * > | <a b=c>
  428. * ^
  429. * > | <a b="c">
  430. * ^
  431. * ```
  432. *
  433. * @type {State}
  434. */
  435. function completeAttributeValueBefore(code) {
  436. if (
  437. code === codes.eof ||
  438. code === codes.lessThan ||
  439. code === codes.equalsTo ||
  440. code === codes.greaterThan ||
  441. code === codes.graveAccent
  442. ) {
  443. return nok(code)
  444. }
  445. if (code === codes.quotationMark || code === codes.apostrophe) {
  446. effects.consume(code)
  447. markerB = code
  448. return completeAttributeValueQuoted
  449. }
  450. if (markdownSpace(code)) {
  451. effects.consume(code)
  452. return completeAttributeValueBefore
  453. }
  454. return completeAttributeValueUnquoted(code)
  455. }
  456. /**
  457. * In double or single quoted attribute value.
  458. *
  459. * ```markdown
  460. * > | <a b="c">
  461. * ^
  462. * > | <a b='c'>
  463. * ^
  464. * ```
  465. *
  466. * @type {State}
  467. */
  468. function completeAttributeValueQuoted(code) {
  469. if (code === markerB) {
  470. effects.consume(code)
  471. markerB = null
  472. return completeAttributeValueQuotedAfter
  473. }
  474. if (code === codes.eof || markdownLineEnding(code)) {
  475. return nok(code)
  476. }
  477. effects.consume(code)
  478. return completeAttributeValueQuoted
  479. }
  480. /**
  481. * In unquoted attribute value.
  482. *
  483. * ```markdown
  484. * > | <a b=c>
  485. * ^
  486. * ```
  487. *
  488. * @type {State}
  489. */
  490. function completeAttributeValueUnquoted(code) {
  491. if (
  492. code === codes.eof ||
  493. code === codes.quotationMark ||
  494. code === codes.apostrophe ||
  495. code === codes.slash ||
  496. code === codes.lessThan ||
  497. code === codes.equalsTo ||
  498. code === codes.greaterThan ||
  499. code === codes.graveAccent ||
  500. markdownLineEndingOrSpace(code)
  501. ) {
  502. return completeAttributeNameAfter(code)
  503. }
  504. effects.consume(code)
  505. return completeAttributeValueUnquoted
  506. }
  507. /**
  508. * After double or single quoted attribute value, before whitespace or the
  509. * end of the tag.
  510. *
  511. * ```markdown
  512. * > | <a b="c">
  513. * ^
  514. * ```
  515. *
  516. * @type {State}
  517. */
  518. function completeAttributeValueQuotedAfter(code) {
  519. if (
  520. code === codes.slash ||
  521. code === codes.greaterThan ||
  522. markdownSpace(code)
  523. ) {
  524. return completeAttributeNameBefore(code)
  525. }
  526. return nok(code)
  527. }
  528. /**
  529. * In certain circumstances of a complete tag where only an `>` is allowed.
  530. *
  531. * ```markdown
  532. * > | <a b="c">
  533. * ^
  534. * ```
  535. *
  536. * @type {State}
  537. */
  538. function completeEnd(code) {
  539. if (code === codes.greaterThan) {
  540. effects.consume(code)
  541. return completeAfter
  542. }
  543. return nok(code)
  544. }
  545. /**
  546. * After `>` in a complete tag.
  547. *
  548. * ```markdown
  549. * > | <x>
  550. * ^
  551. * ```
  552. *
  553. * @type {State}
  554. */
  555. function completeAfter(code) {
  556. if (code === codes.eof || markdownLineEnding(code)) {
  557. // // Do not form containers.
  558. // tokenizer.concrete = true
  559. return continuation(code)
  560. }
  561. if (markdownSpace(code)) {
  562. effects.consume(code)
  563. return completeAfter
  564. }
  565. return nok(code)
  566. }
  567. /**
  568. * In continuation of any HTML kind.
  569. *
  570. * ```markdown
  571. * > | <!--xxx-->
  572. * ^
  573. * ```
  574. *
  575. * @type {State}
  576. */
  577. function continuation(code) {
  578. if (code === codes.dash && marker === constants.htmlComment) {
  579. effects.consume(code)
  580. return continuationCommentInside
  581. }
  582. if (code === codes.lessThan && marker === constants.htmlRaw) {
  583. effects.consume(code)
  584. return continuationRawTagOpen
  585. }
  586. if (code === codes.greaterThan && marker === constants.htmlDeclaration) {
  587. effects.consume(code)
  588. return continuationClose
  589. }
  590. if (code === codes.questionMark && marker === constants.htmlInstruction) {
  591. effects.consume(code)
  592. return continuationDeclarationInside
  593. }
  594. if (code === codes.rightSquareBracket && marker === constants.htmlCdata) {
  595. effects.consume(code)
  596. return continuationCdataInside
  597. }
  598. if (
  599. markdownLineEnding(code) &&
  600. (marker === constants.htmlBasic || marker === constants.htmlComplete)
  601. ) {
  602. effects.exit(types.htmlFlowData)
  603. return effects.check(
  604. blankLineBefore,
  605. continuationAfter,
  606. continuationStart
  607. )(code)
  608. }
  609. if (code === codes.eof || markdownLineEnding(code)) {
  610. effects.exit(types.htmlFlowData)
  611. return continuationStart(code)
  612. }
  613. effects.consume(code)
  614. return continuation
  615. }
  616. /**
  617. * In continuation, at eol.
  618. *
  619. * ```markdown
  620. * > | <x>
  621. * ^
  622. * | asd
  623. * ```
  624. *
  625. * @type {State}
  626. */
  627. function continuationStart(code) {
  628. return effects.check(
  629. nonLazyContinuationStart,
  630. continuationStartNonLazy,
  631. continuationAfter
  632. )(code)
  633. }
  634. /**
  635. * In continuation, at eol, before non-lazy content.
  636. *
  637. * ```markdown
  638. * > | <x>
  639. * ^
  640. * | asd
  641. * ```
  642. *
  643. * @type {State}
  644. */
  645. function continuationStartNonLazy(code) {
  646. assert(markdownLineEnding(code))
  647. effects.enter(types.lineEnding)
  648. effects.consume(code)
  649. effects.exit(types.lineEnding)
  650. return continuationBefore
  651. }
  652. /**
  653. * In continuation, before non-lazy content.
  654. *
  655. * ```markdown
  656. * | <x>
  657. * > | asd
  658. * ^
  659. * ```
  660. *
  661. * @type {State}
  662. */
  663. function continuationBefore(code) {
  664. if (code === codes.eof || markdownLineEnding(code)) {
  665. return continuationStart(code)
  666. }
  667. effects.enter(types.htmlFlowData)
  668. return continuation(code)
  669. }
  670. /**
  671. * In comment continuation, after one `-`, expecting another.
  672. *
  673. * ```markdown
  674. * > | <!--xxx-->
  675. * ^
  676. * ```
  677. *
  678. * @type {State}
  679. */
  680. function continuationCommentInside(code) {
  681. if (code === codes.dash) {
  682. effects.consume(code)
  683. return continuationDeclarationInside
  684. }
  685. return continuation(code)
  686. }
  687. /**
  688. * In raw continuation, after `<`, at `/`.
  689. *
  690. * ```markdown
  691. * > | <script>console.log(1)</script>
  692. * ^
  693. * ```
  694. *
  695. * @type {State}
  696. */
  697. function continuationRawTagOpen(code) {
  698. if (code === codes.slash) {
  699. effects.consume(code)
  700. buffer = ''
  701. return continuationRawEndTag
  702. }
  703. return continuation(code)
  704. }
  705. /**
  706. * In raw continuation, after `</`, in a raw tag name.
  707. *
  708. * ```markdown
  709. * > | <script>console.log(1)</script>
  710. * ^^^^^^
  711. * ```
  712. *
  713. * @type {State}
  714. */
  715. function continuationRawEndTag(code) {
  716. if (code === codes.greaterThan) {
  717. const name = buffer.toLowerCase()
  718. if (htmlRawNames.includes(name)) {
  719. effects.consume(code)
  720. return continuationClose
  721. }
  722. return continuation(code)
  723. }
  724. if (asciiAlpha(code) && buffer.length < constants.htmlRawSizeMax) {
  725. effects.consume(code)
  726. // @ts-expect-error: not null.
  727. buffer += String.fromCharCode(code)
  728. return continuationRawEndTag
  729. }
  730. return continuation(code)
  731. }
  732. /**
  733. * In cdata continuation, after `]`, expecting `]>`.
  734. *
  735. * ```markdown
  736. * > | <![CDATA[>&<]]>
  737. * ^
  738. * ```
  739. *
  740. * @type {State}
  741. */
  742. function continuationCdataInside(code) {
  743. if (code === codes.rightSquareBracket) {
  744. effects.consume(code)
  745. return continuationDeclarationInside
  746. }
  747. return continuation(code)
  748. }
  749. /**
  750. * In declaration or instruction continuation, at `>`.
  751. *
  752. * ```markdown
  753. * > | <!-->
  754. * ^
  755. * > | <?>
  756. * ^
  757. * > | <!q>
  758. * ^
  759. * > | <!--ab-->
  760. * ^
  761. * > | <![CDATA[>&<]]>
  762. * ^
  763. * ```
  764. *
  765. * @type {State}
  766. */
  767. function continuationDeclarationInside(code) {
  768. if (code === codes.greaterThan) {
  769. effects.consume(code)
  770. return continuationClose
  771. }
  772. // More dashes.
  773. if (code === codes.dash && marker === constants.htmlComment) {
  774. effects.consume(code)
  775. return continuationDeclarationInside
  776. }
  777. return continuation(code)
  778. }
  779. /**
  780. * In closed continuation: everything we get until the eol/eof is part of it.
  781. *
  782. * ```markdown
  783. * > | <!doctype>
  784. * ^
  785. * ```
  786. *
  787. * @type {State}
  788. */
  789. function continuationClose(code) {
  790. if (code === codes.eof || markdownLineEnding(code)) {
  791. effects.exit(types.htmlFlowData)
  792. return continuationAfter(code)
  793. }
  794. effects.consume(code)
  795. return continuationClose
  796. }
  797. /**
  798. * Done.
  799. *
  800. * ```markdown
  801. * > | <!doctype>
  802. * ^
  803. * ```
  804. *
  805. * @type {State}
  806. */
  807. function continuationAfter(code) {
  808. effects.exit(types.htmlFlow)
  809. // // Feel free to interrupt.
  810. // tokenizer.interrupt = false
  811. // // No longer concrete.
  812. // tokenizer.concrete = false
  813. return ok(code)
  814. }
  815. }
  816. /**
  817. * @this {TokenizeContext}
  818. * @type {Tokenizer}
  819. */
  820. function tokenizeNonLazyContinuationStart(effects, ok, nok) {
  821. const self = this
  822. return start
  823. /**
  824. * At eol, before continuation.
  825. *
  826. * ```markdown
  827. * > | * ```js
  828. * ^
  829. * | b
  830. * ```
  831. *
  832. * @type {State}
  833. */
  834. function start(code) {
  835. if (markdownLineEnding(code)) {
  836. effects.enter(types.lineEnding)
  837. effects.consume(code)
  838. effects.exit(types.lineEnding)
  839. return after
  840. }
  841. return nok(code)
  842. }
  843. /**
  844. * A continuation.
  845. *
  846. * ```markdown
  847. * | * ```js
  848. * > | b
  849. * ^
  850. * ```
  851. *
  852. * @type {State}
  853. */
  854. function after(code) {
  855. return self.parser.lazy[self.now().line] ? nok(code) : ok(code)
  856. }
  857. }
  858. /**
  859. * @this {TokenizeContext}
  860. * @type {Tokenizer}
  861. */
  862. function tokenizeBlankLineBefore(effects, ok, nok) {
  863. return start
  864. /**
  865. * Before eol, expecting blank line.
  866. *
  867. * ```markdown
  868. * > | <div>
  869. * ^
  870. * |
  871. * ```
  872. *
  873. * @type {State}
  874. */
  875. function start(code) {
  876. assert(markdownLineEnding(code), 'expected a line ending')
  877. effects.enter(types.lineEnding)
  878. effects.consume(code)
  879. effects.exit(types.lineEnding)
  880. return effects.attempt(blankLine, ok, nok)
  881. }
  882. }