Skip to content

Fix unbalanced markdown when an emphasis span is next to a whitespace-only one - #274

Open
youdie006 wants to merge 3 commits into
thephpleague:masterfrom
youdie006:fix/emphasis-whitespace-sibling
Open

Fix unbalanced markdown when an emphasis span is next to a whitespace-only one#274
youdie006 wants to merge 3 commits into
thephpleague:masterfrom
youdie006:fix/emphasis-whitespace-sibling

Conversation

@youdie006

Copy link
Copy Markdown

Fixes #252.

EmphasisConverter has a "merge adjacent same-type emphasis" optimization (added in #202) so that <em>foo</em><em>bar</em> becomes *foobar* rather than *foo**bar*. It suppresses a span's opening marker when the previous sibling is the same emphasis type (and its closing marker when the next sibling is), assuming that neighbour emitted markers of its own.

But a whitespace-only span short-circuits earlier (if (! \trim($value)) { return $value; }) and emits no markers. So when such a span is adjacent to a real one, the real span still drops its marker and the output is unbalanced:

<strong> </strong><strong>hello</strong>   =>   " hello**"   (missing opening **)

Fix: only merge with a same-type sibling that has non-whitespace content (i.e. one that actually emitted markers). Content-bearing merges (<em>foo</em><em>bar</em> -> *foobar*) are unchanged.

Tests: added whitespace-only-neighbour cases to testConsecutiveSpans (<strong> </strong><strong>hello</strong> -> **hello**, plus the <em> variant). Verified red before the fix (" hello**") and green after. composer phpunit (50 tests, 231 assertions), phpstan, and psalm all pass locally.


Disclosure: developed with the assistance of Claude Code (AI); reviewed and verified by me.

The merge-adjacent optimization suppressed a span's opening/closing
emphasis marker whenever the neighbouring sibling was of the same
type, assuming that neighbour emitted its own markers. A
whitespace-only sibling (e.g. <strong> </strong>) returns its bare
value and emits none, so <strong> </strong><strong>hello</strong>
lost its opening ** and produced the unbalanced " hello**".

Only merge when the same-type sibling has non-whitespace content, so
it actually emitted markers. Add regression tests.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes unbalanced Markdown output produced by EmphasisConverter when its “merge adjacent emphasis” optimization encounters an adjacent same-type emphasis span whose content is whitespace-only (and therefore emits no emphasis markers), addressing issue #252.

Changes:

  • Adjusts the adjacent-emphasis merge logic to only merge with same-type siblings that have non-whitespace content.
  • Adds PHPUnit coverage for whitespace-only adjacent emphasis spans in testConsecutiveSpans.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Converter/EmphasisConverter.php Adds a sibling “mergeability” check so marker suppression only happens when the adjacent same-type sibling would actually emit markers.
tests/HtmlConverterTest.php Adds regression tests covering whitespace-only adjacent emphasis spans that previously caused unbalanced markers.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/HtmlConverterTest.php
Comment on lines +71 to +76
private function isMergeableSibling(?ElementInterface $sibling, string $tag): bool
{
return $sibling !== null
&& $this->getNormTag($sibling) === $tag
&& \trim($sibling->getValue()) !== '';
}
colinodell and others added 2 commits August 10, 2026 09:57
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
isMergeableSibling used `trim($value) !== ''` to decide whether a same-type
sibling emits emphasis markers, but convert()'s early return uses
`! \trim($value)`, which is also true for the string "0" (falsy in PHP).
So `<em>0</em><em>hello</em>` treated the "0" sibling as mergeable and
suppressed the adjacent marker, producing the unbalanced `0hello*`.

Mirror convert()'s truthiness check with `(bool) \trim(...)` so a falsy
sibling (whitespace-only or "0") no longer suppresses the neighbour's
markers, and add regression tests for the "0" case in both positions.
@youdie006

Copy link
Copy Markdown
Author

Thanks for the review. Addressed the automated feedback:

  • Falsy "0" content: good catch. isMergeableSibling() used trim(...) !== '', which disagreed with convert()'s ! \trim($value) guard for the string "0" (falsy in PHP), so <em>0</em><em>hello</em> produced the unbalanced 0hello*. Switched to (bool) \trim(...) so the check mirrors convert() exactly, and added regression tests for "0" in both positions (0*hello*, *hello*0).
  • Symmetric (trailing) whitespace sibling: this is already covered by the existing cases <strong>hello</strong><strong> </strong> and <em>hello</em><em> </em> (the $postStyle suppression path), added alongside the leading-whitespace cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Empty tags break siblings' markdown

3 participants