From aecc5c4116418a4f20d84713eef4b596c6b6bb02 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Tue, 29 Sep 2026 11:46:44 +0200 Subject: [PATCH] fix: apply the flanking rules to emphasis delimiters --- src/Rules/BoldAndItalicRule.php | 21 +++++++++-- src/Rules/BoldRule.php | 4 +- src/Rules/ChecksFlanking.php | 23 ++++++++++++ src/Rules/ItalicRule.php | 4 +- tests/EmphasisFlankingTest.php | 66 +++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 src/Rules/ChecksFlanking.php create mode 100644 tests/EmphasisFlankingTest.php diff --git a/src/Rules/BoldAndItalicRule.php b/src/Rules/BoldAndItalicRule.php index f1733a0..9eb1680 100644 --- a/src/Rules/BoldAndItalicRule.php +++ b/src/Rules/BoldAndItalicRule.php @@ -14,15 +14,28 @@ final class BoldAndItalicRule implements ProvidesFirstChar, ProvidesStopChar { + use ChecksFlanking; + private(set) string $firstChar = '*_'; private(set) string $stopChar = '_*'; public function shouldParse(Parser $parser): bool { - return ( - $parser->comesNext('***', length: 3) - || $parser->comesNext('___', length: 3) - ); + if ( + ! $parser->comesNext('***', length: 3) + && ! $parser->comesNext('___', length: 3) + ) { + return false; + } + + $start = $parser->position + 3; + $end = strpos($parser->content, $parser->current ?? '', $start); + + return $this->isFlanked(substr( + $parser->content, + $start, + $end === false ? null : $end - $start, + )); } public function parse(Parser $parser): Token diff --git a/src/Rules/BoldRule.php b/src/Rules/BoldRule.php index de2a7ad..9af743b 100644 --- a/src/Rules/BoldRule.php +++ b/src/Rules/BoldRule.php @@ -11,6 +11,8 @@ final class BoldRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use ChecksFlanking; + private(set) string $firstChar = '*_'; private(set) string $stopChar = '*_'; @@ -49,7 +51,7 @@ public function shouldParse(Parser $parser): bool return false; } - return true; + return $this->isFlanked(substr($content, 0, -1)); } public function parse(Parser $parser): Token diff --git a/src/Rules/ChecksFlanking.php b/src/Rules/ChecksFlanking.php new file mode 100644 index 0000000..18edee7 --- /dev/null +++ b/src/Rules/ChecksFlanking.php @@ -0,0 +1,23 @@ +isFlanked(substr($end, 0, -1)); } public function parse(Parser $parser): Token diff --git a/tests/EmphasisFlankingTest.php b/tests/EmphasisFlankingTest.php new file mode 100644 index 0000000..daa1ea9 --- /dev/null +++ b/tests/EmphasisFlankingTest.php @@ -0,0 +1,66 @@ +assertSame( + $expected, + new Markdown(highlighter: null)->parse($markdown)->html, + ); + } + + public static function provideFlanking(): iterable + { + yield 'italic' => ['*a*', '

a

']; + yield 'bold' => ['**a**', '

a

']; + yield 'bold and italic' => [ + '***a***', + '

a

', + ]; + yield 'underscore italic' => ['_a_', '

a

']; + yield 'underscore bold' => ['__a__', '

a

']; + yield 'inside a word' => ['foo*bar*', '

foobar

']; + + // An opening delimiter may not be followed by whitespace, and a + // closing one may not be preceded by it. + yield 'spaced asterisks' => ['2 * 3 * 4', '

2 * 3 * 4

']; + yield 'spaced double asterisks' => ['a ** b **', '

a ** b **

']; + yield 'spaced triple asterisks' => [ + 'a *** b ***', + '

a *** b ***

', + ]; + yield 'spaced underscores' => ['x _ y _', '

x _ y _

']; + yield 'space before the closing asterisk' => ['*a *', '

*a *

']; + yield 'space before the closing double asterisk' => [ + '**a **', + '

**a **

', + ]; + yield 'space after the opening double asterisk' => [ + '** a**', + '

** a**

', + ]; + yield 'space before the closing double underscore' => [ + '__a __', + '

__a __

', + ]; + yield 'hotel ratings' => [ + 'Hôtels 3*** et 4****', + '

Hôtels 3*** et 4****

', + ]; + yield 'literal run after emphasis' => [ + '*a* * b *', + '

a * b *

', + ]; + } +}