Browse Source

Fix list source maps including trailing blank lines

The list rule recorded map end at the first line that cannot belong to
the list, which includes blank lines after the last item. Trim trailing
empty lines from list and list_item maps only. Leave state.line unchanged
so tightness and HTML stay the same.

Fixes #374

Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
pull/1212/head
Cursor Agent 3 weeks ago
parent
commit
0b45b97b5a
Failed to extract signature
  1. 13
      src/rules_block/list.ts
  2. 84
      test/markdown-it/list-sourcemap.test.mjs

13
src/rules_block/list.ts

@ -88,6 +88,15 @@ function markTightParagraphs (state: StateBlock, idx: number) {
}
}
// Source maps should stop at the last content line, matching other block
// tokens. Do not move state.line: the parser still consumed the blanks.
function trimTrailingBlankLines (state: StateBlock, start: number, end: number): number {
while (end > start + 1 && state.isEmpty(end - 1)) {
end--
}
return end
}
export default function list (state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
let max, pos, start, token
let nextLine = startLine
@ -275,7 +284,7 @@ export default function list (state: StateBlock, startLine: number, endLine: num
token.markup = String.fromCharCode(markerCharCode)
nextLine = state.line
itemLines[1] = nextLine
itemLines[1] = trimTrailingBlankLines(state, itemLines[0], nextLine)
if (nextLine >= endLine) { break }
@ -318,7 +327,7 @@ export default function list (state: StateBlock, startLine: number, endLine: num
}
token.markup = String.fromCharCode(markerCharCode)
listLines[1] = nextLine
listLines[1] = trimTrailingBlankLines(state, listLines[0], nextLine)
state.line = nextLine
state.parentType = oldParentType

84
test/markdown-it/list-sourcemap.test.mjs

@ -0,0 +1,84 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import markdownit from '../../src/index.ts'
describe('List source maps', function () {
const md = markdownit()
function token (tokens, type) {
return tokens.find(t => t.type === type)
}
function tokensOf (src) {
return md.parse(src, {})
}
it('should not include trailing blank lines after a bullet list (#374)', function () {
const tokens = tokensOf('- aaa\n\n\nbbb\n')
assert.deepEqual(token(tokens, 'bullet_list_open').map, [0, 1])
assert.deepEqual(token(tokens, 'list_item_open').map, [0, 1])
assert.deepEqual(token(tokens, 'paragraph_open').map, [0, 1])
})
it('should trim many trailing blank lines from list maps', function () {
const tokens = tokensOf('- aaa\n\n\n\n\n\n\n\n\nbbb\n')
assert.deepEqual(token(tokens, 'bullet_list_open').map, [0, 1])
assert.deepEqual(token(tokens, 'list_item_open').map, [0, 1])
})
it('should not include trailing blank lines after an ordered list', function () {
const tokens = tokensOf('1. aaa\n\n\nbbb\n')
assert.deepEqual(token(tokens, 'ordered_list_open').map, [0, 1])
assert.deepEqual(token(tokens, 'list_item_open').map, [0, 1])
})
it('should keep maps for tight lists without extra blanks', function () {
const tokens = tokensOf('- aaa\n- bbb\n')
const items = tokens.filter(t => t.type === 'list_item_open')
assert.deepEqual(token(tokens, 'bullet_list_open').map, [0, 2])
assert.deepEqual(items[0].map, [0, 1])
assert.deepEqual(items[1].map, [1, 2])
})
it('should exclude blanks after the last item of a multi-item list', function () {
const tokens = tokensOf('- foo\n\n- bar\n\n\nbaz\n')
const items = tokens.filter(t => t.type === 'list_item_open')
assert.deepEqual(token(tokens, 'bullet_list_open').map, [0, 3])
assert.deepEqual(items[0].map, [0, 1])
assert.deepEqual(items[1].map, [2, 3])
})
it('should keep blank lines between paragraphs inside a loose item', function () {
const tokens = tokensOf('- foo\n\n bar\n\n\nbaz\n')
assert.deepEqual(token(tokens, 'bullet_list_open').map, [0, 3])
assert.deepEqual(token(tokens, 'list_item_open').map, [0, 3])
})
it('should not include trailing blanks on nested lists', function () {
const tokens = tokensOf('- foo\n - aaa\n\n- bar\n')
const lists = tokens.filter(t => t.type === 'bullet_list_open')
const items = tokens.filter(t => t.type === 'list_item_open')
assert.deepEqual(lists[0].map, [0, 4])
assert.deepEqual(lists[1].map, [1, 2])
assert.deepEqual(items[1].map, [1, 2])
})
it('should not change rendered HTML', function () {
// Trailing blanks after a list must not loosen it or change markup.
assert.strictEqual(
md.render('- aaa\n\n\nbbb\n'),
'<ul>\n<li>aaa</li>\n</ul>\n<p>bbb</p>\n'
)
assert.strictEqual(
md.render('- foo\n\n bar\n'),
'<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n'
)
})
})
Loading…
Cancel
Save