Skip to content

fix: do not continue a block quote paragraph from a whitespace-only line - #4134

Merged
UziTech merged 1 commit into
markedjs:masterfrom
OsamaAnsar:fix/blockquote-whitespace-only-line
Oct 26, 2026
Merged

UziTech merged 1 commit into
markedjs:masterfrom
OsamaAnsar:fix/blockquote-whitespace-only-line

Conversation

@OsamaAnsar

Copy link
Copy Markdown
Contributor

Marked version: 18.1.1 (master at cdbdc01)

Markdown flavor: CommonMark

Description

  • Fixes #### : no existing issue that I could find, so the issue is described below.

Expectation

Text after a block quote line that has two or more trailing spaces after the > should end the quote, like it does for > and > . CommonMark, commonmark.js and markdown-it all give:

> a
>  
b
<blockquote>
<p>a</p>
</blockquote>
<p>b</p>

Result

On master the unquoted line b is swallowed into the block quote (marked.parse('> a\n> \nb')):

<blockquote>
<p>a
 
b</p>
</blockquote>

With three trailing spaces ('> a\n> \nb') b is also inside the quote and gets a hard line break: <blockquote>\n<p>a\n<br>b</p>\n</blockquote>\n. And '> \nb' (a quote whose only line is whitespace) gives <blockquote>\n<p>b</p>\n</blockquote>\n instead of an empty quote followed by <p>b</p>. '> a\n>\nb' and '> a\n> \nb' already work.

What was attempted

In the blockquote rule, > ? consumes one space after the marker, and a whitespace-only remainder (> leaves a single space) then matches the paragraph alternative, which lazily continues onto the next unquoted line. The fix keeps whitespace-only lines out of that branch with a negative lookahead that guards only the paragraph alternative, so they fall through to the existing [^\n]* branch like > and > do:

-const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/)
+const blockquote = edit(/^( {0,3}> ?(?:(?![ \t]*(?:\n|$))(paragraph)|[^\n]*)(?:\n|$))+/)

After the fix (same inputs): '> a\n> \nb' -> <blockquote>\n<p>a\n </p>\n</blockquote>\n<p>b</p>\n, '> a\n> \nb' -> <blockquote>\n<p>a\n </p>\n</blockquote>\n<p>b</p>\n, '> \nb' -> <blockquote>\n</blockquote>\n<p>b</p>\n. A stray whitespace-only line is still left inside the quote's paragraph (<p>a\n </p>); this PR does not try to remove it. The spec runner's HTML differ ignores that whitespace.

How I checked it:

  • npm test (build:reset, build:docs, specs, unit, umd, cjs, types, lint): exit 0. Specs 1915/1915 and unit 199/199 with the fix; on unmodified master the specs are 1911 pass with my 2 new spec files failing (4 failing test entries: suite + case each), so no existing spec changed.
  • Reverted only the src/rules.ts change, rebuilt, and confirmed the built bundle no longer had the lookahead and reproduced the master output above: both new specs fail. Restored the change, rebuilt: both pass.
  • npm run test:redos: the block.normal.blockquote and block.gfm.blockquote regexes are reported safe with the change.
  • A/B fuzz of master vs this branch over 500,000 random block-structure inputs (quote markers with 0-3 trailing spaces, lists, headings, fences, setext lines, html starts, link definitions, tabs, blank lines), filtered to inputs where commonmark.js and markdown-it produce the same normalized HTML (about 342,000 of them): 0 cases where master matched both and this branch does not; 1,941 cases where master differed from both and this branch matches. Output differed from master on 2,922 inputs in total, including some where the two reference parsers disagree and some where neither version matches them (existing unrelated differences); I did not triage those.
  • Quick timing on a 5,000-repeat blockquote/list/paragraph document, 20 parses each, run twice: master 79.3 / 76.8 ms per parse, this branch 77.4 / 79.4 ms. No measurable difference.

Test files added under test/specs/new/: blockquote_whitespace_only_line (two quotes with 2 and 3 trailing spaces followed by an unquoted line) and blockquote_whitespace_only_empty (> then an unquoted line).

Nearby open PRs: #4110 (tab after the block quote marker) changes Tokenizer.ts/rules.ts around block quote markers but not this regex; I did not find an open PR that changes the blockquote rule.

Contributor

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

Committer

In most cases, this should be a different person than the contributor.

In `> a\n>  \nb` the marker consumes one space and the remaining
whitespace-only text matched the lazy paragraph branch of the blockquote
rule, which then swallowed the following unquoted line into the quote.
A line with only whitespace after the marker is blank and must end the
paragraph, as for `>` and `> `.
@vercel

vercel Bot commented Oct 12, 2026

Copy link
Copy Markdown

@OsamaAnsar 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 Oct 12, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
marked-website Ready Ready Preview Oct 12, 2026 7:16pm UTC

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.

Nice catch! 💯

@UziTech
UziTech merged commit 42f34a8 into markedjs:master Oct 26, 2026
8 checks passed
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
## [18.1.2](v18.1.1...v18.1.2) (2026-10-26)

### Bug Fixes

* allow a tab or end of input after a type 6 html block tag name ([#4128](#4128)) ([e09c40d](e09c40d))
* apply emphasis flanking and rules 9-10 to the whole delimiter run ([#4145](#4145)) ([5d8e234](5d8e234))
* **cli:** await config functions before parsing ([#4138](#4138)) ([3a11ad1](3a11ad1))
* **cli:** create no-clobber output files atomically ([#4135](#4135)) ([e7c4f64](e7c4f64))
* do not continue a block quote paragraph from a whitespace-only line ([#4134](#4134)) ([42f34a8](42f34a8))
* expand a tab after the block quote marker to the next tab stop ([#4110](#4110)) ([2d3899b](2d3899b))
* honor per-call async options when composing hooks ([#4139](#4139)) ([379be9d](379be9d))
* keep a line starting with # but no space in the list item ([#4127](#4127)) ([6b63189](6b63189))
* keep duplicate link definitions in the tokens ([#4132](#4132)) ([2784acc](2784acc))
* keep lazy lines after a list in a nested blockquote ([#4137](#4137)) ([38c7e55](38c7e55))
* keep lazy setext underlines in list items as text ([#4126](#4126)) ([398234c](398234c))
* render nested image alt formatting as plain text ([#4131](#4131)) ([15f59bb](15f59bb))
* stop an unclosed inline raw tag from unescaping later blocks ([#4130](#4130)) ([4562b6f](4562b6f))

### Performance Improvements

* ~16% faster parsing by skipping em/strong masking for runs without *, _ or ~ ([#4142](#4142)) ([f0fd3e0](f0fd3e0))

This branch was successfully deployed

1 active deployment
Preview — 0112ce84 Deployed Oct 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants