Просмотр исходного кода

add a fork of micromark-extension-gfm-table

Yuki Takei 2 лет назад
Родитель
Сommit
3e06cf986a
27 измененных файлов с 2568 добавлено и 1 удалено
  1. 2 1
      apps/app/package.json
  2. 3 0
      packages/micromark-extension-gfm-table/.gitignore
  3. 3 0
      packages/micromark-extension-gfm-table/.prettierignore
  4. 1 0
      packages/micromark-extension-gfm-table/.remarkignore
  5. 2 0
      packages/micromark-extension-gfm-table/dev/index.js
  6. 144 0
      packages/micromark-extension-gfm-table/dev/lib/html.js
  7. 644 0
      packages/micromark-extension-gfm-table/dev/lib/syntax.js
  8. 97 0
      packages/micromark-extension-gfm-table/package.json
  9. 390 0
      packages/micromark-extension-gfm-table/readme.md
  10. 104 0
      packages/micromark-extension-gfm-table/test/fixtures/align.html
  11. 77 0
      packages/micromark-extension-gfm-table/test/fixtures/align.md
  12. 40 0
      packages/micromark-extension-gfm-table/test/fixtures/basic.html
  13. 16 0
      packages/micromark-extension-gfm-table/test/fixtures/basic.md
  14. 132 0
      packages/micromark-extension-gfm-table/test/fixtures/containers.html
  15. 53 0
      packages/micromark-extension-gfm-table/test/fixtures/containers.md
  16. 117 0
      packages/micromark-extension-gfm-table/test/fixtures/gfm.html
  17. 54 0
      packages/micromark-extension-gfm-table/test/fixtures/gfm.md
  18. 85 0
      packages/micromark-extension-gfm-table/test/fixtures/grave.html
  19. 32 0
      packages/micromark-extension-gfm-table/test/fixtures/grave.md
  20. 29 0
      packages/micromark-extension-gfm-table/test/fixtures/indent.html
  21. 17 0
      packages/micromark-extension-gfm-table/test/fixtures/indent.md
  22. 31 0
      packages/micromark-extension-gfm-table/test/fixtures/loose.html
  23. 19 0
      packages/micromark-extension-gfm-table/test/fixtures/loose.md
  24. 27 0
      packages/micromark-extension-gfm-table/test/fixtures/some-escapes.html
  25. 12 0
      packages/micromark-extension-gfm-table/test/fixtures/some-escapes.md
  26. 421 0
      packages/micromark-extension-gfm-table/test/index.js
  27. 16 0
      packages/micromark-extension-gfm-table/tsconfig.json

+ 2 - 1
apps/app/package.json

@@ -61,8 +61,8 @@
     "@google-cloud/storage": "^5.8.5",
     "@growi/core": "^6.1.0-RC.0",
     "@growi/hackmd": "^6.1.0-RC.0",
-    "@growi/remark-attachment-refs": "^6.1.0-RC.0",
     "@growi/preset-themes": "^6.1.0-RC.0",
+    "@growi/remark-attachment-refs": "^6.1.0-RC.0",
     "@growi/remark-drawio": "^6.1.0-RC.0",
     "@growi/remark-growi-directive": "^6.1.0-RC.0",
     "@growi/remark-lsx": "^6.1.0-RC.0",
@@ -120,6 +120,7 @@
     "markdown-table": "^1.1.1",
     "md5": "^2.2.1",
     "method-override": "^3.0.0",
+    "micromark-extension-gfm-table": "link:./packages/micromark-extension-gfm-table",
     "migrate-mongo": "^8.2.3",
     "mkdirp": "^1.0.3",
     "mongoose": "^6.0.13",

+ 3 - 0
packages/micromark-extension-gfm-table/.gitignore

@@ -0,0 +1,3 @@
+*.d.ts
+/lib/
+/index.js

+ 3 - 0
packages/micromark-extension-gfm-table/.prettierignore

@@ -0,0 +1,3 @@
+coverage/
+*.html
+*.md

+ 1 - 0
packages/micromark-extension-gfm-table/.remarkignore

@@ -0,0 +1 @@
+test/

+ 2 - 0
packages/micromark-extension-gfm-table/dev/index.js

@@ -0,0 +1,2 @@
+export {gfmTableHtml} from './lib/html.js'
+export {gfmTable} from './lib/syntax.js'

+ 144 - 0
packages/micromark-extension-gfm-table/dev/lib/html.js

@@ -0,0 +1,144 @@
+/**
+ * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
+ * @typedef {import('./syntax.js').Align} Align
+ */
+
+const alignment = {
+  none: '',
+  left: ' align="left"',
+  right: ' align="right"',
+  center: ' align="center"'
+}
+
+/**
+ * HTML extension for micromark (passed in `htmlExtensions`).
+ *
+ * @type {HtmlExtension}
+ */
+export const gfmTableHtml = {
+  enter: {
+    table(token) {
+      /** @type {Array<Align>} */
+      // @ts-expect-error Custom.
+      const tableAlign = token._align
+      this.lineEndingIfNeeded()
+      this.tag('<table>')
+      this.setData('tableAlign', tableAlign)
+    },
+    tableBody() {
+      // Clear slurping line ending from the delimiter row.
+      this.setData('slurpOneLineEnding')
+      this.tag('<tbody>')
+    },
+    tableData() {
+      const tableAlign = /** @type {Array<Align>} */ (
+        this.getData('tableAlign')
+      )
+      const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
+      const align = alignment[tableAlign[tableColumn]]
+
+      if (align === undefined) {
+        // Capture results to ignore them.
+        this.buffer()
+      } else {
+        this.lineEndingIfNeeded()
+        this.tag('<td' + align + '>')
+      }
+    },
+    tableHead() {
+      this.lineEndingIfNeeded()
+      this.tag('<thead>')
+    },
+    tableHeader() {
+      const tableAlign = /** @type {Array<Align>} */ (
+        this.getData('tableAlign')
+      )
+      const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
+      const align = alignment[tableAlign[tableColumn]]
+
+      this.lineEndingIfNeeded()
+      this.tag('<th' + align + '>')
+    },
+    tableRow() {
+      this.setData('tableColumn', 0)
+      this.lineEndingIfNeeded()
+      this.tag('<tr>')
+    }
+  },
+  exit: {
+    // Overwrite the default code text data handler to unescape escaped pipes when
+    // they are in tables.
+    codeTextData(token) {
+      let value = this.sliceSerialize(token)
+
+      if (this.getData('tableAlign')) {
+        value = value.replace(/\\([\\|])/g, replace)
+      }
+
+      this.raw(this.encode(value))
+    },
+    table() {
+      this.setData('tableAlign')
+      // If there was no table body, make sure the slurping from the delimiter row
+      // is cleared.
+      this.setData('slurpAllLineEndings')
+      this.lineEndingIfNeeded()
+      this.tag('</table>')
+    },
+    tableBody() {
+      this.lineEndingIfNeeded()
+      this.tag('</tbody>')
+    },
+    tableData() {
+      const tableAlign = /** @type {Array<Align>} */ (
+        this.getData('tableAlign')
+      )
+      const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
+
+      if (tableColumn in tableAlign) {
+        this.tag('</td>')
+        this.setData('tableColumn', tableColumn + 1)
+      } else {
+        // Stop capturing.
+        this.resume()
+      }
+    },
+    tableHead() {
+      this.lineEndingIfNeeded()
+      this.tag('</thead>')
+      this.setData('slurpOneLineEnding', true)
+      // Slurp the line ending from the delimiter row.
+    },
+    tableHeader() {
+      const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
+      this.tag('</th>')
+      this.setData('tableColumn', tableColumn + 1)
+    },
+    tableRow() {
+      const tableAlign = /** @type {Array<Align>} */ (
+        this.getData('tableAlign')
+      )
+      let tableColumn = /** @type {number} */ (this.getData('tableColumn'))
+
+      while (tableColumn < tableAlign.length) {
+        this.lineEndingIfNeeded()
+        this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>')
+        tableColumn++
+      }
+
+      this.setData('tableColumn', tableColumn)
+      this.lineEndingIfNeeded()
+      this.tag('</tr>')
+    }
+  }
+}
+
+/**
+ * @param {string} $0
+ * @param {string} $1
+ * @returns {string}
+ */
+function replace($0, $1) {
+  // Pipes work, backslashes don’t (but can’t escape pipes).
+  return $1 === '|' ? $1 : $0
+}

+ 644 - 0
packages/micromark-extension-gfm-table/dev/lib/syntax.js

@@ -0,0 +1,644 @@
+/**
+ * @typedef {import('micromark-util-types').Extension} Extension
+ * @typedef {import('micromark-util-types').Resolver} Resolver
+ * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
+ * @typedef {import('micromark-util-types').State} State
+ * @typedef {import('micromark-util-types').Token} Token
+ */
+
+/**
+ * @typedef {'left'|'center'|'right'|'none'} Align
+ */
+
+import {ok as assert} from 'uvu/assert'
+import {factorySpace} from 'micromark-factory-space'
+import {
+  markdownLineEnding,
+  markdownLineEndingOrSpace,
+  markdownSpace
+} from 'micromark-util-character'
+import {codes} from 'micromark-util-symbol/codes.js'
+import {constants} from 'micromark-util-symbol/constants.js'
+import {types} from 'micromark-util-symbol/types.js'
+
+/**
+ * Syntax extension for micromark (passed in `extensions`).
+ *
+ * @type {Extension}
+ */
+export const gfmTable = {
+  flow: {null: {tokenize: tokenizeTable, resolve: resolveTable}}
+}
+
+const nextPrefixedOrBlank = {
+  tokenize: tokenizeNextPrefixedOrBlank,
+  partial: true
+}
+
+/** @type {Resolver} */
+// eslint-disable-next-line complexity
+function resolveTable(events, context) {
+  let index = -1
+  /** @type {boolean|undefined} */
+  let inHead
+  /** @type {boolean|undefined} */
+  let inDelimiterRow
+  /** @type {boolean|undefined} */
+  let inRow
+  /** @type {number|undefined} */
+  let contentStart
+  /** @type {number|undefined} */
+  let contentEnd
+  /** @type {number|undefined} */
+  let cellStart
+  /** @type {boolean|undefined} */
+  let seenCellInRow
+
+  while (++index < events.length) {
+    const token = events[index][1]
+
+    if (inRow) {
+      if (token.type === 'temporaryTableCellContent') {
+        contentStart = contentStart || index
+        contentEnd = index
+      }
+
+      if (
+        // Combine separate content parts into one.
+        (token.type === 'tableCellDivider' || token.type === 'tableRow') &&
+        contentEnd
+      ) {
+        assert(
+          contentStart,
+          'expected `contentStart` to be defined if `contentEnd` is'
+        )
+        const content = {
+          type: 'tableContent',
+          start: events[contentStart][1].start,
+          end: events[contentEnd][1].end
+        }
+        /** @type {Token} */
+        const text = {
+          type: types.chunkText,
+          start: content.start,
+          end: content.end,
+          // @ts-expect-error It’s fine.
+          contentType: constants.contentTypeText
+        }
+
+        assert(
+          contentStart,
+          'expected `contentStart` to be defined if `contentEnd` is'
+        )
+
+        events.splice(
+          contentStart,
+          contentEnd - contentStart + 1,
+          ['enter', content, context],
+          ['enter', text, context],
+          ['exit', text, context],
+          ['exit', content, context]
+        )
+
+        index -= contentEnd - contentStart - 3
+        contentStart = undefined
+        contentEnd = undefined
+      }
+    }
+
+    if (
+      events[index][0] === 'exit' &&
+      cellStart !== undefined &&
+      cellStart + (seenCellInRow ? 0 : 1) < index &&
+      (token.type === 'tableCellDivider' ||
+        (token.type === 'tableRow' &&
+          (cellStart + 3 < index ||
+            events[cellStart][1].type !== types.whitespace)))
+    ) {
+      const cell = {
+        type: inDelimiterRow
+          ? 'tableDelimiter'
+          : inHead
+          ? 'tableHeader'
+          : 'tableData',
+        start: events[cellStart][1].start,
+        end: events[index][1].end
+      }
+      events.splice(index + (token.type === 'tableCellDivider' ? 1 : 0), 0, [
+        'exit',
+        cell,
+        context
+      ])
+      events.splice(cellStart, 0, ['enter', cell, context])
+      index += 2
+      cellStart = index + 1
+      seenCellInRow = true
+    }
+
+    if (token.type === 'tableRow') {
+      inRow = events[index][0] === 'enter'
+
+      if (inRow) {
+        cellStart = index + 1
+        seenCellInRow = false
+      }
+    }
+
+    if (token.type === 'tableDelimiterRow') {
+      inDelimiterRow = events[index][0] === 'enter'
+
+      if (inDelimiterRow) {
+        cellStart = index + 1
+        seenCellInRow = false
+      }
+    }
+
+    if (token.type === 'tableHead') {
+      inHead = events[index][0] === 'enter'
+    }
+  }
+
+  return events
+}
+
+/** @type {Tokenizer} */
+function tokenizeTable(effects, ok, nok) {
+  const self = this
+  /** @type {Array<Align>} */
+  const align = []
+  let tableHeaderCount = 0
+  /** @type {boolean|undefined} */
+  let seenDelimiter
+  /** @type {boolean|undefined} */
+  let hasDash
+
+  return start
+
+  /** @type {State} */
+  function start(code) {
+    // @ts-expect-error Custom.
+    effects.enter('table')._align = align
+    effects.enter('tableHead')
+    effects.enter('tableRow')
+
+    // If we start with a pipe, we open a cell marker.
+    if (code === codes.verticalBar) {
+      return cellDividerHead(code)
+    }
+
+    tableHeaderCount++
+    effects.enter('temporaryTableCellContent')
+    // Can’t be space or eols at the start of a construct, so we’re in a cell.
+    assert(!markdownLineEndingOrSpace(code), 'expected non-space')
+    return inCellContentHead(code)
+  }
+
+  /** @type {State} */
+  function cellDividerHead(code) {
+    assert(code === codes.verticalBar, 'expected `|`')
+    effects.enter('tableCellDivider')
+    effects.consume(code)
+    effects.exit('tableCellDivider')
+    seenDelimiter = true
+    return cellBreakHead
+  }
+
+  /** @type {State} */
+  function cellBreakHead(code) {
+    if (code === codes.eof || markdownLineEnding(code)) {
+      return atRowEndHead(code)
+    }
+
+    if (markdownSpace(code)) {
+      effects.enter(types.whitespace)
+      effects.consume(code)
+      return inWhitespaceHead
+    }
+
+    if (seenDelimiter) {
+      seenDelimiter = undefined
+      tableHeaderCount++
+    }
+
+    if (code === codes.verticalBar) {
+      return cellDividerHead(code)
+    }
+
+    // Anything else is cell content.
+    effects.enter('temporaryTableCellContent')
+    return inCellContentHead(code)
+  }
+
+  /** @type {State} */
+  function inWhitespaceHead(code) {
+    if (markdownSpace(code)) {
+      effects.consume(code)
+      return inWhitespaceHead
+    }
+
+    effects.exit(types.whitespace)
+    return cellBreakHead(code)
+  }
+
+  /** @type {State} */
+  function inCellContentHead(code) {
+    // EOF, whitespace, pipe
+    if (
+      code === codes.eof ||
+      code === codes.verticalBar ||
+      markdownLineEndingOrSpace(code)
+    ) {
+      effects.exit('temporaryTableCellContent')
+      return cellBreakHead(code)
+    }
+
+    effects.consume(code)
+    return code === codes.backslash
+      ? inCellContentEscapeHead
+      : inCellContentHead
+  }
+
+  /** @type {State} */
+  function inCellContentEscapeHead(code) {
+    if (code === codes.backslash || code === codes.verticalBar) {
+      effects.consume(code)
+      return inCellContentHead
+    }
+
+    // Anything else.
+    return inCellContentHead(code)
+  }
+
+  /** @type {State} */
+  function atRowEndHead(code) {
+    if (code === codes.eof) {
+      return nok(code)
+    }
+
+    assert(markdownLineEnding(code), 'expected eol')
+    effects.exit('tableRow')
+    effects.exit('tableHead')
+    const originalInterrupt = self.interrupt
+    self.interrupt = true
+    return effects.attempt(
+      {tokenize: tokenizeRowEnd, partial: true},
+      function (code) {
+        self.interrupt = originalInterrupt
+        effects.enter('tableDelimiterRow')
+        return atDelimiterRowBreak(code)
+      },
+      function (code) {
+        self.interrupt = originalInterrupt
+        return nok(code)
+      }
+    )(code)
+  }
+
+  /** @type {State} */
+  function atDelimiterRowBreak(code) {
+    if (code === codes.eof || markdownLineEnding(code)) {
+      return rowEndDelimiter(code)
+    }
+
+    if (markdownSpace(code)) {
+      effects.enter(types.whitespace)
+      effects.consume(code)
+      return inWhitespaceDelimiter
+    }
+
+    if (code === codes.dash) {
+      effects.enter('tableDelimiterFiller')
+      effects.consume(code)
+      hasDash = true
+      align.push('none')
+      return inFillerDelimiter
+    }
+
+    if (code === codes.colon) {
+      effects.enter('tableDelimiterAlignment')
+      effects.consume(code)
+      effects.exit('tableDelimiterAlignment')
+      align.push('left')
+      return afterLeftAlignment
+    }
+
+    // If we start with a pipe, we open a cell marker.
+    if (code === codes.verticalBar) {
+      effects.enter('tableCellDivider')
+      effects.consume(code)
+      effects.exit('tableCellDivider')
+      return atDelimiterRowBreak
+    }
+
+    return nok(code)
+  }
+
+  /** @type {State} */
+  function inWhitespaceDelimiter(code) {
+    if (markdownSpace(code)) {
+      effects.consume(code)
+      return inWhitespaceDelimiter
+    }
+
+    effects.exit(types.whitespace)
+    return atDelimiterRowBreak(code)
+  }
+
+  /** @type {State} */
+  function inFillerDelimiter(code) {
+    if (code === codes.dash) {
+      effects.consume(code)
+      return inFillerDelimiter
+    }
+
+    effects.exit('tableDelimiterFiller')
+
+    if (code === codes.colon) {
+      effects.enter('tableDelimiterAlignment')
+      effects.consume(code)
+      effects.exit('tableDelimiterAlignment')
+
+      align[align.length - 1] =
+        align[align.length - 1] === 'left' ? 'center' : 'right'
+
+      return afterRightAlignment
+    }
+
+    return atDelimiterRowBreak(code)
+  }
+
+  /** @type {State} */
+  function afterLeftAlignment(code) {
+    if (code === codes.dash) {
+      effects.enter('tableDelimiterFiller')
+      effects.consume(code)
+      hasDash = true
+      return inFillerDelimiter
+    }
+
+    // Anything else is not ok.
+    return nok(code)
+  }
+
+  /** @type {State} */
+  function afterRightAlignment(code) {
+    if (code === codes.eof || markdownLineEnding(code)) {
+      return rowEndDelimiter(code)
+    }
+
+    if (markdownSpace(code)) {
+      effects.enter(types.whitespace)
+      effects.consume(code)
+      return inWhitespaceDelimiter
+    }
+
+    // `|`
+    if (code === codes.verticalBar) {
+      effects.enter('tableCellDivider')
+      effects.consume(code)
+      effects.exit('tableCellDivider')
+      return atDelimiterRowBreak
+    }
+
+    return nok(code)
+  }
+
+  /** @type {State} */
+  function rowEndDelimiter(code) {
+    effects.exit('tableDelimiterRow')
+
+    // Exit if there was no dash at all, or if the header cell count is not the
+    // delimiter cell count.
+    if (!hasDash || tableHeaderCount !== align.length) {
+      return nok(code)
+    }
+
+    if (code === codes.eof) {
+      return tableClose(code)
+    }
+
+    assert(markdownLineEnding(code), 'expected eol')
+    return effects.check(
+      nextPrefixedOrBlank,
+      tableClose,
+      effects.attempt(
+        {tokenize: tokenizeRowEnd, partial: true},
+        factorySpace(effects, bodyStart, types.linePrefix, constants.tabSize),
+        tableClose
+      )
+    )(code)
+  }
+
+  /** @type {State} */
+  function tableClose(code) {
+    effects.exit('table')
+    return ok(code)
+  }
+
+  /** @type {State} */
+  function bodyStart(code) {
+    effects.enter('tableBody')
+    return rowStartBody(code)
+  }
+
+  /** @type {State} */
+  function rowStartBody(code) {
+    effects.enter('tableRow')
+
+    // If we start with a pipe, we open a cell marker.
+    if (code === codes.verticalBar) {
+      return cellDividerBody(code)
+    }
+
+    effects.enter('temporaryTableCellContent')
+    // Can’t be space or eols at the start of a construct, so we’re in a cell.
+    return inCellContentBody(code)
+  }
+
+  /** @type {State} */
+  function cellDividerBody(code) {
+    assert(code === codes.verticalBar, 'expected `|`')
+    effects.enter('tableCellDivider')
+    effects.consume(code)
+    effects.exit('tableCellDivider')
+    return cellBreakBody
+  }
+
+  /** @type {State} */
+  function cellBreakBody(code) {
+    if (code === codes.eof || markdownLineEnding(code)) {
+      return atRowEndBody(code)
+    }
+
+    if (markdownSpace(code)) {
+      effects.enter(types.whitespace)
+      effects.consume(code)
+      return inWhitespaceBody
+    }
+
+    // `|`
+    if (code === codes.verticalBar) {
+      return cellDividerBody(code)
+    }
+
+    // Anything else is cell content.
+    effects.enter('temporaryTableCellContent')
+    return inCellContentBody(code)
+  }
+
+  /** @type {State} */
+  function inWhitespaceBody(code) {
+    if (markdownSpace(code)) {
+      effects.consume(code)
+      return inWhitespaceBody
+    }
+
+    effects.exit(types.whitespace)
+    return cellBreakBody(code)
+  }
+
+  /** @type {State} */
+  function inCellContentBody(code) {
+    // EOF, whitespace, pipe
+    if (
+      code === codes.eof ||
+      code === codes.verticalBar ||
+      markdownLineEndingOrSpace(code)
+    ) {
+      effects.exit('temporaryTableCellContent')
+      return cellBreakBody(code)
+    }
+
+    effects.consume(code)
+    return code === codes.backslash
+      ? inCellContentEscapeBody
+      : inCellContentBody
+  }
+
+  /** @type {State} */
+  function inCellContentEscapeBody(code) {
+    if (code === codes.backslash || code === codes.verticalBar) {
+      effects.consume(code)
+      return inCellContentBody
+    }
+
+    // Anything else.
+    return inCellContentBody(code)
+  }
+
+  /** @type {State} */
+  function atRowEndBody(code) {
+    effects.exit('tableRow')
+
+    if (code === codes.eof) {
+      return tableBodyClose(code)
+    }
+
+    return effects.check(
+      nextPrefixedOrBlank,
+      tableBodyClose,
+      effects.attempt(
+        {tokenize: tokenizeRowEnd, partial: true},
+        factorySpace(
+          effects,
+          rowStartBody,
+          types.linePrefix,
+          constants.tabSize
+        ),
+        tableBodyClose
+      )
+    )(code)
+  }
+
+  /** @type {State} */
+  function tableBodyClose(code) {
+    effects.exit('tableBody')
+    return tableClose(code)
+  }
+
+  /** @type {Tokenizer} */
+  function tokenizeRowEnd(effects, ok, nok) {
+    return start
+
+    /** @type {State} */
+    function start(code) {
+      assert(markdownLineEnding(code), 'expected eol')
+      effects.enter(types.lineEnding)
+      effects.consume(code)
+      effects.exit(types.lineEnding)
+      return factorySpace(effects, prefixed, types.linePrefix)
+    }
+
+    /** @type {State} */
+    function prefixed(code) {
+      // Blank or interrupting line.
+      if (
+        self.parser.lazy[self.now().line] ||
+        code === codes.eof ||
+        markdownLineEnding(code)
+      ) {
+        return nok(code)
+      }
+
+      const tail = self.events[self.events.length - 1]
+
+      // Indented code can interrupt delimiter and body rows.
+      if (
+        !self.parser.constructs.disable.null.includes('codeIndented') &&
+        tail &&
+        tail[1].type === types.linePrefix &&
+        tail[2].sliceSerialize(tail[1], true).length >= constants.tabSize
+      ) {
+        return nok(code)
+      }
+
+      self._gfmTableDynamicInterruptHack = true
+
+      return effects.check(
+        self.parser.constructs.flow,
+        function (code) {
+          self._gfmTableDynamicInterruptHack = false
+          return nok(code)
+        },
+        function (code) {
+          self._gfmTableDynamicInterruptHack = false
+          return ok(code)
+        }
+      )(code)
+    }
+  }
+}
+
+/** @type {Tokenizer} */
+function tokenizeNextPrefixedOrBlank(effects, ok, nok) {
+  let size = 0
+
+  return start
+
+  /** @type {State} */
+  function start(code) {
+    // This is a check, so we don’t care about tokens, but we open a bogus one
+    // so we’re valid.
+    effects.enter('check')
+    // EOL.
+    effects.consume(code)
+    return whitespace
+  }
+
+  /** @type {State} */
+  function whitespace(code) {
+    if (code === codes.virtualSpace || code === codes.space) {
+      effects.consume(code)
+      size++
+      return size === constants.tabSize ? ok : whitespace
+    }
+
+    // EOF or whitespace
+    if (code === codes.eof || markdownLineEndingOrSpace(code)) {
+      return ok(code)
+    }
+
+    // Anything else.
+    return nok(code)
+  }
+}

+ 97 - 0
packages/micromark-extension-gfm-table/package.json

@@ -0,0 +1,97 @@
+{
+  "name": "micromark-extension-gfm-table",
+  "version": "1.0.5",
+  "description": "micromark extension to support GFM tables",
+  "license": "MIT",
+  "keywords": [
+    "micromark",
+    "micromark-extension",
+    "table",
+    "row",
+    "column",
+    "cell",
+    "tabular",
+    "gfm",
+    "markdown",
+    "unified"
+  ],
+  "repository": "micromark/micromark-extension-gfm-table",
+  "bugs": "https://github.com/micromark/micromark-extension-gfm-table/issues",
+  "funding": {
+    "type": "opencollective",
+    "url": "https://opencollective.com/unified"
+  },
+  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
+  "contributors": [
+    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
+  ],
+  "sideEffects": false,
+  "type": "module",
+  "main": "index.js",
+  "types": "index.d.ts",
+  "files": [
+    "dev/",
+    "lib/",
+    "index.d.ts",
+    "index.js"
+  ],
+  "exports": {
+    "development": "./dev/index.js",
+    "default": "./index.js"
+  },
+  "dependencies": {
+    "micromark-factory-space": "^1.0.0",
+    "micromark-util-character": "^1.0.0",
+    "micromark-util-symbol": "^1.0.0",
+    "micromark-util-types": "^1.0.0",
+    "uvu": "^0.5.0"
+  },
+  "devDependencies": {
+    "@types/tape": "^4.0.0",
+    "c8": "^7.0.0",
+    "create-gfm-fixtures": "^1.0.0",
+    "micromark": "^3.0.0",
+    "micromark-build": "^1.0.0",
+    "prettier": "^2.0.0",
+    "remark-cli": "^11.0.0",
+    "remark-preset-wooorm": "^9.0.0",
+    "rimraf": "^3.0.0",
+    "tape": "^5.0.0",
+    "type-coverage": "^2.0.0",
+    "typescript": "^4.0.0",
+    "xo": "^0.50.0"
+  },
+  "scripts": {
+    "build": "rimraf \"dev/**/*.d.ts\" \"test/**/*.d.ts\" && tsc && type-coverage && micromark-build",
+    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
+    "test-api": "node --conditions development test/index.js",
+    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test/index.js",
+    "test": "npm run build && npm run format && npm run test-coverage"
+  },
+  "prettier": {
+    "tabWidth": 2,
+    "useTabs": false,
+    "singleQuote": true,
+    "bracketSpacing": false,
+    "semi": false,
+    "trailingComma": "none"
+  },
+  "xo": {
+    "prettier": true,
+    "rules": {
+      "n/file-extension-in-import": "off",
+      "unicorn/no-this-assignment": "off"
+    }
+  },
+  "remarkConfig": {
+    "plugins": [
+      "preset-wooorm"
+    ]
+  },
+  "typeCoverage": {
+    "atLeast": 100,
+    "detail": true,
+    "strict": true,
+    "ignoreCatch": true
+  }
+}

+ 390 - 0
packages/micromark-extension-gfm-table/readme.md

@@ -0,0 +1,390 @@
+# micromark-extension-gfm-table
+
+[![Build][build-badge]][build]
+[![Coverage][coverage-badge]][coverage]
+[![Downloads][downloads-badge]][downloads]
+[![Size][size-badge]][size]
+[![Sponsors][sponsors-badge]][collective]
+[![Backers][backers-badge]][collective]
+[![Chat][chat-badge]][chat]
+
+[micromark][] extension to support GFM [tables][].
+
+## Contents
+
+*   [What is this?](#what-is-this)
+*   [When to use this](#when-to-use-this)
+*   [Install](#install)
+*   [Use](#use)
+*   [API](#api)
+    *   [`gfmTable`](#gfmtable)
+    *   [`gfmTableHtml`](#gfmtablehtml)
+*   [Authoring](#authoring)
+*   [HTML](#html)
+*   [CSS](#css)
+*   [Syntax](#syntax)
+*   [Types](#types)
+*   [Compatibility](#compatibility)
+*   [Security](#security)
+*   [Related](#related)
+*   [Contribute](#contribute)
+*   [License](#license)
+
+## What is this?
+
+This package contains extensions that add support for tables enabled by
+GFM to [`micromark`][micromark].
+It matches how tables work on `github.com`.
+
+## When to use this
+
+These tools are all low-level.
+In many cases, you want to use [`remark-gfm`][plugin] with remark instead.
+
+Even when you want to use `micromark`, you likely want to use
+[`micromark-extension-gfm`][micromark-extension-gfm] to support all GFM
+features.
+That extension includes this extension.
+
+When working with `mdast-util-from-markdown`, you must combine this package with
+[`mdast-util-gfm-table`][util].
+
+## Install
+
+This package is [ESM only][esm].
+In Node.js (version 12.20+, 14.14+, 16.0+, or 18.0+), install with [npm][]:
+
+```sh
+npm install micromark-extension-gfm-table
+```
+
+In Deno with [`esm.sh`][esmsh]:
+
+```js
+import {gfmTable, gfmTableHtml} from 'https://esm.sh/micromark-extension-gfm-table@1'
+```
+
+In browsers with [`esm.sh`][esmsh]:
+
+```html
+<script type="module">
+  import {gfmTable, gfmTableHtml} from 'https://esm.sh/micromark-extension-gfm-table@1?bundle'
+</script>
+```
+
+## Use
+
+```js
+import {micromark} from 'micromark'
+import {gfmTable, gfmTableHtml} from 'micromark-extension-gfm-table'
+
+const output = micromark('| a |\n| - |', {
+  extensions: [gfmTable],
+  htmlExtensions: [gfmTableHtml]
+})
+
+console.log(output)
+```
+
+Yields:
+
+```html
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+</table>
+```
+
+## API
+
+This package exports the identifiers `gfmTable` and `gfmTableHtml`.
+There is no default export.
+
+The export map supports the endorsed [`development` condition][condition].
+Run `node --conditions development module.js` to get instrumented dev code.
+Without this condition, production code is loaded.
+
+### `gfmTable`
+
+Syntax extension for micromark (passed in `extensions`).
+
+### `gfmTableHtml`
+
+HTML extension for micromark (passed in `htmlExtensions`).
+
+## Authoring
+
+###### Align
+
+When authoring markdown with tables, it can get a bit hard to make them look
+well.
+You can align the pipes (`|`) in rows nicely, which makes it easier to spot
+problems, but aligning gets cumbersome for tables with many rows or columns,
+or when they change frequently, especially if data in cells have varying
+lengths.
+To illustrate, when some cell increases in size which makes it longer than all
+other cells in that column, you’d have to pad every other cell as well, which
+can be a lot of work, and will introduce a giant diff in Git.
+
+###### Initial and final pipes
+
+In some cases, GFM tables can be written without initial or final pipes:
+
+```markdown
+name  | letter
+----- | ------
+alpha | a
+bravo | b
+```
+
+These tables do not parse in certain other cases, making them fragile and hard
+to get right.
+Due to this, it’s recommended to always include initial and final pipes.
+
+###### Escaped pipes in code
+
+GitHub applies one weird, special thing in tables that markdown otherwise never
+does: it allows character escapes (not character references) of pipes (not other
+characters) in code in cells.
+It’s weird, because markdown, per CommonMark, does not allow character escapes
+in code.
+GitHub only applies this change in code in tables:
+
+```markdown
+| `a\|b\-` |
+| - |
+
+`a\|b\-`
+```
+
+Yields:
+
+```html
+<table>
+<thead>
+<tr>
+<th><code>a|b\-</code></th>
+</tr>
+</thead>
+</table>
+<p><code>a\|b\-</code></p>
+```
+
+> 👉 **Note**: observe that the escaped pipe in the table does not result in
+> another column, and is not present in the resulting code.
+> Other escapes, and pipe escapes outside tables, do nothing.
+
+This behavior solves a real problem, so you might resort to using it.
+It might not work in other markdown parsers though.
+
+## HTML
+
+GFM tables relate to several tabular data HTML elements:
+See [*§ 4.9.1 The `table` element*][html-table],
+[*§ 4.9.5 The `tbody` element*][html-tbody],
+[*§ 4.9.6 The `thead` element*][html-thead],
+[*§ 4.9.8 The `tr` element*][html-tr],
+[*§ 4.9.9 The `td` element*][html-td], and
+[*§ 4.9.10 The `th` element*][html-th]
+in the HTML spec for more info.
+
+GitHub provides the alignment information from the delimiter row on each `<td>`
+and `<th>` element with an `align` attribute.
+This feature stems from ancient times in HTML, and still works, but is
+considered a [non-conforming feature][html-non-conform], which must not be used
+by authors.
+
+## CSS
+
+The following CSS is needed to make tables look a bit like GitHub.
+For the complete actual CSS see
+[`sindresorhus/github-markdown-css`][github-markdown-css]
+
+```css
+/* Light theme. */
+:root {
+  --color-canvas-default: #ffffff;
+  --color-canvas-subtle: #f6f8fa;
+  --color-border-default: #d0d7de;
+  --color-border-muted: hsla(210, 18%, 87%, 1);
+}
+
+/* Dark theme. */
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-canvas-default: #0d1117;
+    --color-canvas-subtle: #161b22;
+    --color-border-default: #30363d;
+    --color-border-muted: #21262d;
+  }
+}
+
+table {
+  border-spacing: 0;
+  border-collapse: collapse;
+  display: block;
+  margin-top: 0;
+  margin-bottom: 16px;
+  width: max-content;
+  max-width: 100%;
+  overflow: auto;
+}
+
+tr {
+  background-color: var(--color-canvas-default);
+  border-top: 1px solid var(--color-border-muted);
+}
+
+tr:nth-child(2n) {
+  background-color: var(--color-canvas-subtle);
+}
+
+td,
+th {
+  padding: 6px 13px;
+  border: 1px solid var(--color-border-default);
+}
+
+th {
+  font-weight: 600;
+}
+
+table img {
+  background-color: transparent;
+}
+```
+
+## Syntax
+
+Tables form with, roughly, the following BNF:
+
+```bnf
+; Restriction: number of cells in first row must match number of cells in delimiter row.
+table ::= row eol delimiter_row 0.*( eol row )
+
+; Restriction: Line cannot be blank.
+row ::= [ '|' ] cell 0.*( '|' cell ) [ '|' ]
+delimiter_row ::= [ '|' ] delimiter_cell 0.*( '|' delimiter_cell ) [ '|' ]
+
+cell ::= 0.*space_or_tab 0.*( cell_text | cell_escape ) 0.*space_or_tab
+cell_text ::= code - eol - '|' - '\\' - ''
+cell_escape ::= '\\' ( '|' | '\\' )
+delimiter_cell ::= 0.*space_or_tab [ ':' ] 1*'-' [ ':' ] 0.*space_or_tab
+```
+
+## Types
+
+This package is fully typed with [TypeScript][].
+There are no additional exported types.
+
+## Compatibility
+
+This package is at least compatible with all maintained versions of Node.js.
+As of now, that is Node.js 12.20+, 14.14+, 16.0+, and 18.0+.
+It also works in Deno and modern browsers.
+
+## Security
+
+This package is safe.
+
+## Related
+
+*   [`syntax-tree/mdast-util-gfm-table`][util]
+    — support GFM tables in mdast
+*   [`syntax-tree/mdast-util-gfm`][mdast-util-gfm]
+    — support GFM in mdast
+*   [`remarkjs/remark-gfm`][plugin]
+    — support GFM in remark
+
+## Contribute
+
+See [`contributing.md` in `micromark/.github`][contributing] for ways to get
+started.
+See [`support.md`][support] for ways to get help.
+
+This project has a [code of conduct][coc].
+By interacting with this repository, organization, or community you agree to
+abide by its terms.
+
+## License
+
+[MIT][license] © [Titus Wormer][author]
+
+<!-- Definitions -->
+
+[build-badge]: https://github.com/micromark/micromark-extension-gfm-table/workflows/main/badge.svg
+
+[build]: https://github.com/micromark/micromark-extension-gfm-table/actions
+
+[coverage-badge]: https://img.shields.io/codecov/c/github/micromark/micromark-extension-gfm-table.svg
+
+[coverage]: https://codecov.io/github/micromark/micromark-extension-gfm-table
+
+[downloads-badge]: https://img.shields.io/npm/dm/micromark-extension-gfm-table.svg
+
+[downloads]: https://www.npmjs.com/package/micromark-extension-gfm-table
+
+[size-badge]: https://img.shields.io/bundlephobia/minzip/micromark-extension-gfm-table.svg
+
+[size]: https://bundlephobia.com/result?p=micromark-extension-gfm-table
+
+[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
+
+[backers-badge]: https://opencollective.com/unified/backers/badge.svg
+
+[collective]: https://opencollective.com/unified
+
+[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
+
+[chat]: https://github.com/micromark/micromark/discussions
+
+[npm]: https://docs.npmjs.com/cli/install
+
+[esmsh]: https://esm.sh
+
+[license]: license
+
+[author]: https://wooorm.com
+
+[contributing]: https://github.com/micromark/.github/blob/main/contributing.md
+
+[support]: https://github.com/micromark/.github/blob/main/support.md
+
+[coc]: https://github.com/micromark/.github/blob/main/code-of-conduct.md
+
+[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
+
+[typescript]: https://www.typescriptlang.org
+
+[condition]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions
+
+[micromark]: https://github.com/micromark/micromark
+
+[micromark-extension-gfm]: https://github.com/micromark/micromark-extension-gfm
+
+[util]: https://github.com/syntax-tree/mdast-util-gfm-table
+
+[mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm
+
+[plugin]: https://github.com/remarkjs/remark-gfm
+
+[tables]: https://github.github.com/gfm/#tables-extension-
+
+[html-table]: https://html.spec.whatwg.org/multipage/tables.html#the-table-element
+
+[html-tbody]: https://html.spec.whatwg.org/multipage/tables.html#the-tbody-element
+
+[html-thead]: https://html.spec.whatwg.org/multipage/tables.html#the-thead-element
+
+[html-tr]: https://html.spec.whatwg.org/multipage/tables.html#the-tr-element
+
+[html-td]: https://html.spec.whatwg.org/multipage/tables.html#the-td-element
+
+[html-th]: https://html.spec.whatwg.org/multipage/tables.html#the-th-element
+
+[html-non-conform]: https://html.spec.whatwg.org/multipage/obsolete.html#non-conforming-features
+
+[github-markdown-css]: https://github.com/sindresorhus/github-markdown-css

+ 104 - 0
packages/micromark-extension-gfm-table/test/fixtures/align.html

@@ -0,0 +1,104 @@
+<h1>Align</h1>
+<h2>An empty initial cell</h2>
+<table>
+<thead>
+<tr>
+<th></th>
+<th align="center">a</th>
+<th align="left">c</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>a</td>
+<td align="center">b</td>
+<td align="left">c</td>
+</tr>
+<tr>
+<td>a</td>
+<td align="center">b</td>
+<td align="left">c</td>
+</tr>
+</tbody>
+</table>
+<h2>Missing alignment characters</h2>
+<p>| a | b | c |
+|   |---|---|
+| d | e | f |</p>
+<hr />
+<p>| a | b | c |
+|---|---|   |
+| d | e | f |</p>
+<h2>Incorrect characters</h2>
+<p>| a | b | c |
+|---|-*-|---|
+| d | e | f |</p>
+<h2>Two alignments</h2>
+<p>|a|
+|::|</p>
+<table>
+<thead>
+<tr>
+<th align="center">a</th>
+</tr>
+</thead>
+</table>
+<h2>Two at the start or end</h2>
+<p>|a|
+|::-|</p>
+<p>|a|
+|-::|</p>
+<h2>In the middle</h2>
+<p>|a|
+|-:-|</p>
+<h2>A space in the middle</h2>
+<p>|a|
+|- -|</p>
+<h2>No pipe</h2>
+<table>
+<thead>
+<tr>
+<th align="center">a</th>
+</tr>
+</thead>
+</table>
+<table>
+<thead>
+<tr>
+<th align="left">a</th>
+</tr>
+</thead>
+</table>
+<table>
+<thead>
+<tr>
+<th align="right">a</th>
+</tr>
+</thead>
+</table>
+<h2>A single colon</h2>
+<p>|a|
+|:|</p>
+<p>a
+:</p>
+<h2>Alignment on empty cells</h2>
+<table>
+<thead>
+<tr>
+<th>a</th>
+<th>b</th>
+<th align="left">c</th>
+<th align="right">d</th>
+<th align="center">e</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>f</td>
+<td></td>
+<td align="left"></td>
+<td align="right"></td>
+<td align="center"></td>
+</tr>
+</tbody>
+</table>

+ 77 - 0
packages/micromark-extension-gfm-table/test/fixtures/align.md

@@ -0,0 +1,77 @@
+# Align
+
+## An empty initial cell
+
+| | a|c|
+|--|:----:|:---|
+|a|b|c|
+|a|b|c|
+
+## Missing alignment characters
+
+| a | b | c |
+|   |---|---|
+| d | e | f |
+
+* * *
+
+| a | b | c |
+|---|---|   |
+| d | e | f |
+
+## Incorrect characters
+
+| a | b | c |
+|---|-*-|---|
+| d | e | f |
+
+## Two alignments
+
+|a|
+|::|
+
+|a|
+|:-:|
+
+## Two at the start or end
+
+|a|
+|::-|
+
+|a|
+|-::|
+
+## In the middle
+
+|a|
+|-:-|
+
+## A space in the middle
+
+|a|
+|- -|
+
+## No pipe
+
+a
+:-:
+
+a
+:-
+
+a
+-:
+
+## A single colon
+
+|a|
+|:|
+
+a
+:
+
+## Alignment on empty cells
+
+| a | b | c | d | e |
+| - | - | :- | -: | :-: |
+| f |

+ 40 - 0
packages/micromark-extension-gfm-table/test/fixtures/basic.html

@@ -0,0 +1,40 @@
+<h1>Tables</h1>
+<table>
+<thead>
+<tr>
+<th>a</th>
+<th>b</th>
+<th>c</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>d</td>
+<td>e</td>
+<td>f</td>
+</tr>
+</tbody>
+</table>
+<h2>No body</h2>
+<table>
+<thead>
+<tr>
+<th>a</th>
+<th>b</th>
+<th>c</th>
+</tr>
+</thead>
+</table>
+<h2>One column</h2>
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>b</td>
+</tr>
+</tbody>
+</table>

+ 16 - 0
packages/micromark-extension-gfm-table/test/fixtures/basic.md

@@ -0,0 +1,16 @@
+# Tables
+
+| a | b | c |
+| - | - | - |
+| d | e | f |
+
+## No body
+
+| a | b | c |
+| - | - | - |
+
+## One column
+
+| a |
+| - |
+| b |

+ 132 - 0
packages/micromark-extension-gfm-table/test/fixtures/containers.html

@@ -0,0 +1,132 @@
+<h1>Tables in things</h1>
+<h2>In lists</h2>
+<ul>
+<li>
+<p>Unordered:</p>
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>1</td>
+<td>2</td>
+</tr>
+</tbody>
+</table>
+</li>
+</ul>
+<ol>
+<li>
+<p>Ordered:</p>
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>1</td>
+<td>2</td>
+</tr>
+</tbody>
+</table>
+</li>
+</ol>
+<ul>
+<li>Lazy?
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+</table>
+</li>
+</ul>
+<p>| 1 | 2 |
+| 3 | 4 |
+| 5 | 6 |
+| 7 | 8 |</p>
+<h2>In block quotes</h2>
+<blockquote>
+<p>W/ space:</p>
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>1</td>
+<td>2</td>
+</tr>
+</tbody>
+</table>
+</blockquote>
+<blockquote>
+<p>W/o space:</p>
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>1</td>
+<td>2</td>
+</tr>
+</tbody>
+</table>
+</blockquote>
+<blockquote>
+<p>Lazy?</p>
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>1</td>
+<td>2</td>
+</tr>
+<tr>
+<td>3</td>
+<td>4</td>
+</tr>
+</tbody>
+</table>
+</blockquote>
+<p>| 5 | 6 |</p>
+<h3>List interrupting delimiters</h3>
+<p>a |</p>
+<ul>
+<li>|</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+</table>
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+</table>

+ 53 - 0
packages/micromark-extension-gfm-table/test/fixtures/containers.md

@@ -0,0 +1,53 @@
+# Tables in things
+
+## In lists
+
+*   Unordered:
+
+    | A | B |
+    | - | - |
+    | 1 | 2 |
+
+1.  Ordered:
+
+    | A | B |
+    | - | - |
+    | 1 | 2 |
+
+*   Lazy?
+    | A | B |
+    | - | - |
+   | 1 | 2 |
+  | 3 | 4 |
+ | 5 | 6 |
+| 7 | 8 |
+
+## In block quotes
+
+> W/ space:
+> | A | B |
+> | - | - |
+> | 1 | 2 |
+
+>W/o space:
+>| A | B |
+>| - | - |
+>| 1 | 2 |
+
+> Lazy?
+> | A | B |
+> | - | - |
+> | 1 | 2 |
+>| 3 | 4 |
+| 5 | 6 |
+
+### List interrupting delimiters
+
+a |
+- |
+
+a
+-|
+
+a
+|-

+ 117 - 0
packages/micromark-extension-gfm-table/test/fixtures/gfm.html

@@ -0,0 +1,117 @@
+<h1>Examples from GFM</h1>
+<h2>A</h2>
+<table>
+<thead>
+<tr>
+<th>foo</th>
+<th>bar</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>baz</td>
+<td>bim</td>
+</tr>
+</tbody>
+</table>
+<h2>B</h2>
+<table>
+<thead>
+<tr>
+<th align="center">abc</th>
+<th align="right">defghi</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="center">bar</td>
+<td align="right">baz</td>
+</tr>
+</tbody>
+</table>
+<h2>C</h2>
+<table>
+<thead>
+<tr>
+<th>f|oo</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>b <code>|</code> az</td>
+</tr>
+<tr>
+<td>b <strong>|</strong> im</td>
+</tr>
+</tbody>
+</table>
+<h2>D</h2>
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+<td>baz</td>
+</tr>
+</tbody>
+</table>
+<blockquote>
+<p>bar</p>
+</blockquote>
+<h2>E</h2>
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+<td>baz</td>
+</tr>
+<tr>
+<td>bar</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+<p>bar</p>
+<h2>F</h2>
+<p>| abc | def |
+| --- |
+| bar |</p>
+<h2>G</h2>
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+<td></td>
+</tr>
+<tr>
+<td>bar</td>
+<td>baz</td>
+</tr>
+</tbody>
+</table>
+<h2>H</h2>
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+</table>

+ 54 - 0
packages/micromark-extension-gfm-table/test/fixtures/gfm.md

@@ -0,0 +1,54 @@
+# Examples from GFM
+
+## A
+
+| foo | bar |
+| --- | --- |
+| baz | bim |
+
+## B
+
+| abc | defghi |
+:-: | -----------:
+bar | baz
+
+## C
+
+| f\|oo  |
+| ------ |
+| b `\|` az |
+| b **\|** im |
+
+## D
+
+| abc | def |
+| --- | --- |
+| bar | baz |
+> bar
+
+## E
+
+| abc | def |
+| --- | --- |
+| bar | baz |
+bar
+
+bar
+
+## F
+
+| abc | def |
+| --- |
+| bar |
+
+## G
+
+| abc | def |
+| --- | --- |
+| bar |
+| bar | baz | boo |
+
+## H
+
+| abc | def |
+| --- | --- |

+ 85 - 0
packages/micromark-extension-gfm-table/test/fixtures/grave.html

@@ -0,0 +1,85 @@
+<h1>Grave accents</h1>
+<h2>Grave accent in cell</h2>
+<table>
+<thead>
+<tr>
+<th>A</th>
+<th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><kbd>`</kbd></td>
+<td>C</td>
+</tr>
+</tbody>
+</table>
+<h2>Escaped grave accent in “inline code” in cell</h2>
+<table>
+<thead>
+<tr>
+<th>A</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><code>\</code></td>
+</tr>
+</tbody>
+</table>
+<h2>“Empty” inline code</h2>
+<table>
+<thead>
+<tr>
+<th>1</th>
+<th>2</th>
+<th>3</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>a</td>
+<td>``</td>
+<td></td>
+</tr>
+<tr>
+<td>b</td>
+<td>``</td>
+<td>``</td>
+</tr>
+<tr>
+<td>c</td>
+<td>`</td>
+<td>`</td>
+</tr>
+<tr>
+<td>d</td>
+<td>`</td>
+<td>`</td>
+</tr>
+<tr>
+<td>e</td>
+<td><code>|</code></td>
+<td></td>
+</tr>
+<tr>
+<td>f</td>
+<td>|</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+<h2>Escaped pipes in code in cells</h2>
+<table>
+<thead>
+<tr>
+<th><code>|\\</code></th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><code>|\\</code></td>
+</tr>
+</tbody>
+</table>
+<p><code>\|\\</code></p>

+ 32 - 0
packages/micromark-extension-gfm-table/test/fixtures/grave.md

@@ -0,0 +1,32 @@
+# Grave accents
+
+## Grave accent in cell
+
+| A            | B |
+|--------------|---|
+| <kbd>`</kbd> | C |
+
+## Escaped grave accent in “inline code” in cell
+
+| A   |
+|-----|
+| `\` |
+
+## “Empty” inline code
+
+| 1 | 2    | 3  |
+|---|------|----|
+| a |   `` |    |
+| b |   `` | `` |
+| c |    ` | `  |
+| d |     `|`   |
+| e | `\|` |    |
+| f |   \| |    |
+
+## Escaped pipes in code in cells
+
+| `\|\\` |
+| --- |
+| `\|\\` |
+
+`\|\\`

+ 29 - 0
packages/micromark-extension-gfm-table/test/fixtures/indent.html

@@ -0,0 +1,29 @@
+<h1>Code</h1>
+<h2>Indented delimiter row</h2>
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+</table>
+<p>a
+|-</p>
+<h2>Indented body</h2>
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>C</td>
+</tr>
+<tr>
+<td>D</td>
+</tr>
+</tbody>
+</table>
+<pre><code>| E |
+</code></pre>

+ 17 - 0
packages/micromark-extension-gfm-table/test/fixtures/indent.md

@@ -0,0 +1,17 @@
+# Code
+
+## Indented delimiter row
+
+a
+   |-
+
+a
+    |-
+
+## Indented body
+
+| a |
+ | - |
+  | C |
+   | D |
+    | E |

+ 31 - 0
packages/micromark-extension-gfm-table/test/fixtures/loose.html

@@ -0,0 +1,31 @@
+<h1>Loose</h1>
+<h2>Loose</h2>
+<table>
+<thead>
+<tr>
+<th>Header 1</th>
+<th>Header 2</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Cell 1</td>
+<td>Cell 2</td>
+</tr>
+<tr>
+<td>Cell 3</td>
+<td>Cell 4</td>
+</tr>
+</tbody>
+</table>
+<h2>One “column”, loose</h2>
+<h2>a</h2>
+<p>b</p>
+<h2>No pipe in first row</h2>
+<table>
+<thead>
+<tr>
+<th>a</th>
+</tr>
+</thead>
+</table>

+ 19 - 0
packages/micromark-extension-gfm-table/test/fixtures/loose.md

@@ -0,0 +1,19 @@
+# Loose
+
+## Loose
+
+Header 1 | Header 2
+-------- | --------
+Cell 1   | Cell 2
+Cell 3   | Cell 4
+
+## One “column”, loose
+
+a
+-
+b
+
+## No pipe in first row
+
+a
+| - |

+ 27 - 0
packages/micromark-extension-gfm-table/test/fixtures/some-escapes.html

@@ -0,0 +1,27 @@
+<h1>Some more escapes</h1>
+<table>
+<thead>
+<tr>
+<th>Head</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>A</td>
+</tr>
+<tr>
+<td>B | Bravo</td>
+</tr>
+<tr>
+<td>C | Charlie</td>
+</tr>
+<tr>
+<td>D \| Delta</td>
+</tr>
+<tr>
+<td>E \| Echo</td>
+</tr>
+</tbody>
+</table>
+<p>Note: GH has a bug where in case C and E, the escaped escape is treated as a
+normal escape.</p>

+ 12 - 0
packages/micromark-extension-gfm-table/test/fixtures/some-escapes.md

@@ -0,0 +1,12 @@
+# Some more escapes
+
+| Head          |
+| ------------- |
+| A | Alpha     |
+| B \| Bravo    |
+| C \\| Charlie |
+| D \\\| Delta  |
+| E \\\\| Echo  |
+
+Note: GH has a bug where in case C and E, the escaped escape is treated as a
+normal escape.

+ 421 - 0
packages/micromark-extension-gfm-table/test/index.js

@@ -0,0 +1,421 @@
+import {URL} from 'node:url'
+import fs from 'node:fs'
+import path from 'node:path'
+import test from 'tape'
+import {micromark} from 'micromark'
+import {createGfmFixtures} from 'create-gfm-fixtures'
+import {gfmTable as syntax, gfmTableHtml as html} from '../dev/index.js'
+
+test('markdown -> html (micromark)', (t) => {
+  t.deepEqual(
+    micromark('| a |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<p>| a |</p>',
+    'should not support a table w/ the head row ending in an eof (1)'
+  )
+
+  t.deepEqual(
+    micromark('| a', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<p>| a</p>',
+    'should not support a table w/ the head row ending in an eof (2)'
+  )
+
+  t.deepEqual(
+    micromark('a |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<p>a |</p>',
+    'should not support a table w/ the head row ending in an eof (3)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should support a table w/ a delimiter row ending in an eof (1)'
+  )
+
+  t.deepEqual(
+    micromark('| a\n| -', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should support a table w/ a delimiter row ending in an eof (2)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n| b |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n</tbody>\n</table>',
+    'should support a table w/ a body row ending in an eof (1)'
+  )
+
+  t.deepEqual(
+    micromark('| a\n| -\n| b', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n</tbody>\n</table>',
+    'should support a table w/ a body row ending in an eof (2)'
+  )
+
+  t.deepEqual(
+    micromark('a|b\n-|-\nc|d', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>c</td>\n<td>d</td>\n</tr>\n</tbody>\n</table>',
+    'should support a table w/ a body row ending in an eof (3)'
+  )
+
+  t.deepEqual(
+    micromark('| a  \n| -\t\n| b |     ', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n</tbody>\n</table>',
+    'should support rows w/ trailing whitespace (1)'
+  )
+
+  t.deepEqual(
+    micromark('| a | \n| - |', {extensions: [syntax], htmlExtensions: [html]}),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should support rows w/ trailing whitespace (2)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - | ', {extensions: [syntax], htmlExtensions: [html]}),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should support rows w/ trailing whitespace (3)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n| b | ', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n</tbody>\n</table>',
+    'should support rows w/ trailing whitespace (4)'
+  )
+
+  t.deepEqual(
+    micromark('||a|\n|-|-|', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th></th>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should support empty first header cells'
+  )
+
+  t.deepEqual(
+    micromark('|a||\n|-|-|', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n<th></th>\n</tr>\n</thead>\n</table>',
+    'should support empty last header cells'
+  )
+
+  t.deepEqual(
+    micromark('a||b\n-|-|-', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n<th></th>\n<th>b</th>\n</tr>\n</thead>\n</table>',
+    'should support empty header cells'
+  )
+
+  t.deepEqual(
+    micromark('|a|b|\n|-|-|\n||c|', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td></td>\n<td>c</td>\n</tr>\n</tbody>\n</table>',
+    'should support empty first body cells'
+  )
+
+  t.deepEqual(
+    micromark('|a|b|\n|-|-|\n|c||', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>c</td>\n<td></td>\n</tr>\n</tbody>\n</table>',
+    'should support empty last body cells'
+  )
+
+  t.deepEqual(
+    micromark('a|b|c\n-|-|-\nd||e', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>d</td>\n<td></td>\n<td>e</td>\n</tr>\n</tbody>\n</table>',
+    'should support empty body cells'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n- b', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<ul>\n<li>b</li>\n</ul>',
+    'should support a list after a table'
+  )
+
+  t.deepEqual(
+    micromark('> | a |\n| - |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<blockquote>\n<p>| a |\n| - |</p>\n</blockquote>',
+    'should not support a lazy delimiter row (1)'
+  )
+
+  t.deepEqual(
+    micromark('> a\n> | b |\n| - |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<blockquote>\n<p>a\n| b |\n| - |</p>\n</blockquote>',
+    'should not support a lazy delimiter row (2)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n> | - |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<p>| a |</p>\n<blockquote>\n<p>| - |</p>\n</blockquote>',
+    'should not support a lazy delimiter row (3)'
+  )
+
+  t.deepEqual(
+    micromark('> a\n> | b |\n|-', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<blockquote>\n<p>a\n| b |\n|-</p>\n</blockquote>',
+    'should not support a lazy delimiter row (4)'
+  )
+
+  t.deepEqual(
+    micromark('> | a |\n> | - |\n| b |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<blockquote>\n<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n</blockquote>\n<p>| b |</p>',
+    'should not support a lazy body row (1)'
+  )
+
+  t.deepEqual(
+    micromark('> a\n> | b |\n> | - |\n| c |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<blockquote>\n<p>a</p>\n<table>\n<thead>\n<tr>\n<th>b</th>\n</tr>\n</thead>\n</table>\n</blockquote>\n<p>| c |</p>',
+    'should not support a lazy body row (2)'
+  )
+
+  t.deepEqual(
+    micromark('> | A |\n> | - |\n> | 1 |\n| 2 |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<blockquote>\n<table>\n<thead>\n<tr>\n<th>A</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1</td>\n</tr>\n</tbody>\n</table>\n</blockquote>\n<p>| 2 |</p>',
+    'should not support a lazy body row (3)'
+  )
+
+  const doc = '   - d\n    - e'
+
+  t.deepEqual(
+    micromark(doc, {extensions: [syntax], htmlExtensions: [html]}),
+    micromark(doc),
+    'should not change how lists and lazyness work'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n   | - |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should form a table if the delimiter row is indented w/ 3 spaces'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n    | - |', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<p>| a |\n| - |</p>',
+    'should not form a table if the delimiter row is indented w/ 4 spaces'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n    | - |', {
+      extensions: [syntax, {disable: {null: ['codeIndented']}}],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>',
+    'should form a table if the delimiter row is indented w/ 4 spaces and indented code is turned off'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n> block quote?', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<blockquote>\n<p>block quote?</p>\n</blockquote>',
+    'should be interrupted by a block quote'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n>', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<blockquote>\n</blockquote>',
+    'should be interrupted by a block quote (empty)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n- list?', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<ul>\n<li>list?</li>\n</ul>',
+    'should be interrupted by a list'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n-', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<ul>\n<li></li>\n</ul>',
+    'should be interrupted by a list (empty)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n<!-- HTML? -->', {
+      allowDangerousHtml: true,
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<!-- HTML? -->',
+    'should be interrupted by HTML (flow)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n\tcode?', {
+      allowDangerousHtml: true,
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<pre><code>code?\n</code></pre>',
+    'should be interrupted by code (indented)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n```js\ncode?', {
+      allowDangerousHtml: true,
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<pre><code class="language-js">code?\n</code></pre>\n',
+    'should be interrupted by code (fenced)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n***', {
+      allowDangerousHtml: true,
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<hr />',
+    'should be interrupted by a thematic break'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\n# heading?', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n</table>\n<h1>heading?</h1>',
+    'should be interrupted by a heading (ATX)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\nheading\n=', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>heading</td>\n</tr>\n<tr>\n<td>=</td>\n</tr>\n</tbody>\n</table>',
+    'should *not* be interrupted by a heading (setext)'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\nheading\n---', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>heading</td>\n</tr>\n</tbody>\n</table>\n<hr />',
+    'should *not* be interrupted by a heading (setext), but interrupt if the underline is also a thematic break'
+  )
+
+  t.deepEqual(
+    micromark('| a |\n| - |\nheading\n-', {
+      extensions: [syntax],
+      htmlExtensions: [html]
+    }),
+    '<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>heading</td>\n</tr>\n</tbody>\n</table>\n<ul>\n<li></li>\n</ul>',
+    'should *not* be interrupted by a heading (setext), but interrupt if the underline is also an empty list item bullet'
+  )
+
+  t.end()
+})
+
+test('fixtures', async (t) => {
+  const base = new URL('fixtures/', import.meta.url)
+
+  await createGfmFixtures(base, {rehypeStringify: {closeSelfClosing: true}})
+
+  const files = fs.readdirSync(base).filter((d) => /\.md$/.test(d))
+  let index = -1
+
+  while (++index < files.length) {
+    const name = path.basename(files[index], '.md')
+    const input = fs.readFileSync(new URL(name + '.md', base))
+    let expected = String(fs.readFileSync(new URL(name + '.html', base)))
+    let actual = micromark(input, {
+      allowDangerousHtml: true,
+      allowDangerousProtocol: true,
+      extensions: [syntax],
+      htmlExtensions: [html]
+    })
+
+    if (actual && !/\n$/.test(actual)) {
+      actual += '\n'
+    }
+
+    if (name === 'some-escapes') {
+      expected = expected
+        .replace(/C \| Charlie/, 'C \\')
+        .replace(/E \\\| Echo/, 'E \\\\')
+    }
+
+    t.deepEqual(actual, expected, name)
+  }
+
+  t.end()
+})

+ 16 - 0
packages/micromark-extension-gfm-table/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "include": ["dev/**/*.js", "test/**/*.js"],
+  "compilerOptions": {
+    "target": "ES2020",
+    "lib": ["ES2020"],
+    "module": "ES2020",
+    "moduleResolution": "node",
+    "allowJs": true,
+    "checkJs": true,
+    "declaration": true,
+    "emitDeclarationOnly": true,
+    "allowSyntheticDefaultImports": true,
+    "skipLibCheck": true,
+    "strict": true
+  }
+}