Skip to content

fix: require whitespace after an ATX heading marker - #43

Merged
brendt merged 1 commit into
tempestphp:mainfrom
RobinDev:fix/atx-heading-space
Sep 16, 2026
Merged

brendt merged 1 commit into
tempestphp:mainfrom
RobinDev:fix/atx-heading-space

Conversation

@RobinDev

Copy link
Copy Markdown
Contributor

Closes part of #37 (point 4).

HeadingRule::shouldParse() only checked that the line starts with #, so any #-prefixed word became a heading:

new Markdown(highlighter: null)->parse('#titre')->html;   // <h1 id="titre">titre</h1>
new Markdown(highlighter: null)->parse('#hashtag')->html; // <h1 id="hashtag">hashtag</h1>

CommonMark §4.2: an ATX heading is "a string of characters, parsed as inline content, between an opening sequence of 1–6 unescaped # characters and an optional closing sequence", and "the opening sequence of # characters must be followed by spaces or tabs, or by the end of line".

shouldParse() now checks both conditions:

Input Before After
#titre <h1 id="titre">titre</h1> <p>#titre</p>
#5 bolt <h1 id="5-bolt">5 bolt</h1> <p>#5 bolt</p>
####### seven <h7> <p>####### seven</p>
# Titre <h1 id="titre">Titre</h1> unchanged
# <h1></h1> unchanged

The seven-marker case also stops emitting an <h7> element, which isn't HTML.

composer test (331 tests), lint and analyse are clean.

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Benchmark Results

Comparison of fix/atx-heading-space against main (8449b00fcd8912b6050f53baa7795e8cdf45ac70).

Open to see the benchmark results

No benchmark changes above ±5%.

Generated by phpbench against commit 8e924a0

@brendt

brendt commented Sep 15, 2026

Copy link
Copy Markdown
Member

Please fix the merge conflicts before merging

@RobinDev
RobinDev force-pushed the fix/atx-heading-space branch from 6faff73 to 8e924a0 Compare September 15, 2026 12:11
@RobinDev

RobinDev commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on main, conflicts resolved — the overlap was with the generateIds constructor from #46 and the extra test cases, both kept.

While re-checking it against a personal dataset of real-world Markdown, this turns out to change a few hundred documents, all in the same direction. A line starting with #[ — a link at the very start of a line — is currently swallowed as a heading:

#[Book now](#reservation)
main <h1 id="reservation)"><a href="">Book now</a></h1>
this PR <p>#<a href="#reservation">Book now</a></p>

Note the href="": the heading rule consumes the whole line before the link rule sees it, so the destination is lost and the remainder of the line ends up inside the id attribute. The second column is what league/commonmark produces.

@brendt
brendt merged commit b4cd28e into tempestphp:main Sep 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants