Skip to content

fix: keep empty list after blockquote as a sibling block - #4004

Merged
UziTech merged 1 commit into
markedjs:masterfrom
spokodev:fix/empty-list-after-blockquote
Jul 14, 2026
Merged

fix: keep empty list after blockquote as a sibling block#4004
UziTech merged 1 commit into
markedjs:masterfrom
spokodev:fix/empty-list-after-blockquote

Conversation

@spokodev

Copy link
Copy Markdown
Contributor

Description

A blockquote immediately followed by a bare list marker line nests an empty list inside the blockquote instead of starting a sibling list.

Minimal repro

marked.parse('> foo\n-');

Expected (matches commonmark.js and markdown-it):

<blockquote>
<p>foo</p>
</blockquote>
<ul>
<li></li>
</ul>

Actual:

<blockquote>
<p>foo</p>
<ul>
<li></li>
</ul>
</blockquote>

Reproduces for -, *, +, 1., 1) and for multi line blockquotes (> foo\n> bar\n-). Non empty lists (> foo\n- bar) already behave correctly.

Root cause

The blockquote rule reuses the paragraph list interrupt clause:

 {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]

The trailing [ \t]+[^ \t\n] requires content after the marker, which is correct for a top level paragraph (an empty list cannot interrupt a paragraph). But the same clause also controls when a blockquote stops its lazy continuation. A bare marker line does not match it, so the marker line is lazily continued into the blockquote paragraph and then re lexed into a nested empty list.

Per CommonMark 0.31.2 (5.1 block quotes laziness, 5.2 list items) a list begun by a bare marker is a sibling block after the blockquote, not a lazy continuation of it. commonmark.js and markdown-it both produce the sibling.

Fix

Give the blockquote its own paragraph variant (blockquoteParagraph) whose list interrupt clause also matches a bare marker:

 {0,3}(?:[*+-]|1[.)])(?:[ \t]|\n|$)

The top level paragraph rule is left untouched, so foo\n- (setext heading), foo\n* and foo\n1. (no interruption) keep their current behavior. The change only affects when a blockquote ends.

Relation to #4003

#4003 also routes the blockquote rule through a blockquoteParagraph const, but for a different bug (#4002, non empty ordered lists with a start other than 1). It keeps the [ \t]+[^ \t\n] content requirement, so it does not fix the empty marker case verified here. The two changes touch the same region; I can rebase or fold the two interrupt clauses into one if a maintainer prefers a single edit.

Tests

  • Added test/specs/new/empty_list_after_blockquote.{md,html} covering the bare unordered and bare ordered cases including a multi line blockquote.
  • npm test green, including the CommonMark and GFM conformance specs (1745 spec assertions, 188 unit tests).

Contributor

  • Test(s) exist to ensure functionality and minimize regression.
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

Committer

  • CI is green (no forced merge required).
  • Squash and Merge PR following conventional commit guidelines.
@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

@spokodev is attempting to deploy a commit to the MarkedJS Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marked-website Ready Ready Preview, Comment Jul 2, 2026 3:47pm

Request Review

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this has a merge conflict because of #4003

Other than that this looks good

A blockquote followed by a bare list marker line (for example
`> foo\n-`) wrongly nested an empty list inside the blockquote.

The blockquote regex reuses the paragraph list-interrupt clause
` {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]`, whose trailing `[ \t]+[^ \t\n]`
requires content after the marker. A bare marker line therefore was
not seen as an interruption and got lazily continued into the
blockquote paragraph, then re-lexed into a nested empty list.

Give the blockquote its own paragraph variant whose list-interrupt
clause also matches a bare marker, so the list ends the blockquote and
becomes a sibling block. The top level paragraph rule is unchanged, so
an empty list still cannot interrupt a paragraph.
@spokodev
spokodev force-pushed the fix/empty-list-after-blockquote branch from 9701add to 5e5bd1f Compare June 30, 2026 09:48
@spokodev

Copy link
Copy Markdown
Contributor Author

Rebased onto master and resolved the conflict with #4003. The two fixes are compatible: I kept #4003's createParagraph helper and its any-number blockquote interruption, and folded in the bare-marker case so an empty list marker after a blockquote still starts a sibling list. Both cases pass together (> foo + 2. bar gives <ol start="2">, > foo + - gives an empty <ul>). Full spec suite green (1749) plus unit (188).

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this! 💯

@UziTech
UziTech requested review from calculuschild and styfle July 2, 2026 15:50
@UziTech
UziTech merged commit 3f144a0 into markedjs:master Jul 14, 2026
8 checks passed
github-actions Bot pushed a commit that referenced this pull request Jul 21, 2026
## [18.0.7](v18.0.6...v18.0.7) (2026-07-21)

### Bug Fixes

* Avoid O(n^2) backtracking in HTML block close and tilde interrupt regexes ([#4014](#4014)) ([f945fc5](f945fc5)), closes [#3991](#3991)
* Avoid O(n^2) masked source rebuild in inline tokenizer ([#4017](#4017)) ([9154f8f](9154f8f))
* keep empty list after blockquote as a sibling block ([#4004](#4004)) ([3f144a0](3f144a0))
* preserve code spans adjacent to tildes ([#4012](#4012)) ([0de7188](0de7188))
* Recognize setext headings whose first line starts with # ([#4015](#4015)) ([f056437](f056437)), closes [#1](#1)
* treat a line of only tabs as a blank line between paragraphs ([#4007](#4007)) ([bc2f121](bc2f121))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants