Skip to content

Commit

Permalink
Fix interruption of tables
Browse files Browse the repository at this point in the history
Closes GH-4.
  • Loading branch information
wooorm committed Nov 19, 2021
1 parent c3b4c5f commit cac636d
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 74 deletions.
118 changes: 44 additions & 74 deletions dev/lib/syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ export const gfmTable = {
flow: {null: {tokenize: tokenizeTable, resolve: resolveTable}}
}

const setextUnderlineMini = {
tokenize: tokenizeSetextUnderlineMini,
partial: true
}
const nextPrefixedOrBlank = {
tokenize: tokenizeNextPrefixedOrBlank,
partial: true
Expand Down Expand Up @@ -277,39 +273,22 @@ function tokenizeTable(effects, ok, nok) {
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},
atDelimiterLineStart,
nok
)(code)
}

/** @type {State} */
function atDelimiterLineStart(code) {
return effects.check(
setextUnderlineMini,
nok,
// Support an indent before the delimiter row.
factorySpace(
effects,
rowStartDelimiter,
types.linePrefix,
constants.tabSize
)
function (code) {
self.interrupt = originalInterrupt
effects.enter('tableDelimiterRow')
return atDelimiterRowBreak(code)
},
function (code) {
self.interrupt = originalInterrupt
return nok(code)
}
)(code)
}

/** @type {State} */
function rowStartDelimiter(code) {
// If there’s another space, or we’re at the EOL/EOF, exit.
if (code === codes.eof || markdownLineEndingOrSpace(code)) {
return nok(code)
}

effects.enter('tableDelimiterRow')
return atDelimiterRowBreak(code)
}

/** @type {State} */
function atDelimiterRowBreak(code) {
if (code === codes.eof || markdownLineEnding(code)) {
Expand Down Expand Up @@ -582,55 +561,46 @@ function tokenizeTable(effects, ok, nok) {
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return lineStart
return factorySpace(effects, prefixed, types.linePrefix)
}

/** @type {State} */
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code)
}
}
}

// Based on micromark, but that won’t work as we’re in a table, and that expects
// content.
// <https://github.com/micromark/micromark/blob/main/lib/tokenize/setext-underline.js>
/** @type {Tokenizer} */
function tokenizeSetextUnderlineMini(effects, ok, nok) {
return start

/** @type {State} */
function start(code) {
if (code !== codes.dash) {
return nok(code)
}

effects.enter('setextUnderline')
return sequence(code)
}

/** @type {State} */
function sequence(code) {
if (code === codes.dash) {
effects.consume(code)
return sequence
}
function prefixed(code) {
// Blank or interrupting line.
if (
self.parser.lazy[self.now().line] ||
code === codes.eof ||
markdownLineEnding(code)
) {
return nok(code)
}

return whitespace(code)
}
const tail = self.events[self.events.length - 1]

/** @type {State} */
function whitespace(code) {
if (code === codes.eof || markdownLineEnding(code)) {
return ok(code)
}
// 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)
}

if (markdownSpace(code)) {
effects.consume(code)
return whitespace
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)
}

return nok(code)
}
}

Expand Down
139 changes: 139 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,144 @@ test('markdown -> html (micromark)', (t) => {
'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()
})

0 comments on commit cac636d

Please sign in to comment.