From 60996979007c87278b5b9de0d13f90f1af594801 Mon Sep 17 00:00:00 2001 From: Sarath Francis Date: Mon, 1 Jun 2026 22:39:04 -0400 Subject: [PATCH] fix: parse empty list item with trailing space A bare list marker followed only by a space (e.g. `- ` or `1. `) was not recognized as a list and fell through to a paragraph, so `- ` rendered as `

-

` instead of an empty list item. A marker with no trailing space (`-`, `1.`) and an empty item that continues an existing list were already handled correctly, making the behavior inconsistent. The block `list` rule required content after the marker's whitespace (`[ \t][^\n]+?`), so a marker followed only by whitespace failed to match. Relax it to `[ \t][^\n]*?` so a marker with trailing whitespace and no content still starts a list, matching the CommonMark reference and markdown-it. To keep CommonMark's rule that an empty list item cannot interrupt a paragraph, the paragraph list-interrupt patterns now require a non-blank character after the marker, so `foo\n+ ` stays a single paragraph while `foo\n- bar` still interrupts. --- src/rules.ts | 8 ++++---- .../new/list_item_empty_trailing_space.html | 16 ++++++++++++++++ test/specs/new/list_item_empty_trailing_space.md | 13 +++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 test/specs/new/list_item_empty_trailing_space.html create mode 100644 test/specs/new/list_item_empty_trailing_space.md diff --git a/src/rules.ts b/src/rules.ts index b480d03504..cc2e62bccf 100644 --- a/src/rules.ts +++ b/src/rules.ts @@ -136,7 +136,7 @@ const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: + .replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/) .getRegex(); -const list = edit(/^(bull)([ \t][^\n]+?)?(?:\n|$)/) +const list = edit(/^(bull)([ \t][^\n]*?)?(?:\n|$)/) .replace(/bull/g, bullet) .getRegex(); @@ -170,7 +170,7 @@ const paragraph = edit(_paragraph) .replace('|table', '') .replace('blockquote', ' {0,3}>') .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]') // only lists starting from 1 can interrupt + .replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]') // only non-empty lists starting from 1 can interrupt .replace('html', ')|<(?:script|pre|style|textarea|!--)') .replace('tag', _tag) // pars can be interrupted by type (6) html blocks .getRegex(); @@ -214,7 +214,7 @@ const gfmTable = edit( .replace('blockquote', ' {0,3}>') .replace('code', '(?: {4}| {0,3}\t)[^\\n]') .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]') // only lists starting from 1 can interrupt + .replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]') // any bullet ends the table rows .replace('html', ')|<(?:script|pre|style|textarea|!--)') .replace('tag', _tag) // tables can be interrupted by type (6) html blocks .getRegex(); @@ -230,7 +230,7 @@ const blockGfm: Record = { .replace('table', gfmTable) // interrupt paragraphs with table .replace('blockquote', ' {0,3}>') .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]') // only lists starting from 1 can interrupt + .replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]') // only non-empty lists starting from 1 can interrupt .replace('html', ')|<(?:script|pre|style|textarea|!--)') .replace('tag', _tag) // pars can be interrupted by type (6) html blocks .getRegex(), diff --git a/test/specs/new/list_item_empty_trailing_space.html b/test/specs/new/list_item_empty_trailing_space.html new file mode 100644 index 0000000000..6c5cd4fa7e --- /dev/null +++ b/test/specs/new/list_item_empty_trailing_space.html @@ -0,0 +1,16 @@ + + +
    +
  1. +
+
    +
  1. +
+

foo ++

+

bar +1.

diff --git a/test/specs/new/list_item_empty_trailing_space.md b/test/specs/new/list_item_empty_trailing_space.md new file mode 100644 index 0000000000..778c083162 --- /dev/null +++ b/test/specs/new/list_item_empty_trailing_space.md @@ -0,0 +1,13 @@ +- + +* + +1. + +1) + +foo ++ + +bar +1.