diff --git a/mago.toml b/mago.toml index b6b4876..6c4cbfb 100644 --- a/mago.toml +++ b/mago.toml @@ -46,6 +46,7 @@ interface-name = { psr = false } trait-name = { psr = false } class-name = { psr = false } literal-named-argument = { enabled = false } # todo +no-insecure-comparison = { enabled = false } no-error-control-operator = { enabled = false } too-many-methods = { enabled = false } kan-defect = { enabled = false } diff --git a/src/Rules/BoldRule.php b/src/Rules/BoldRule.php index 8fbe9f3..3906dcb 100644 --- a/src/Rules/BoldRule.php +++ b/src/Rules/BoldRule.php @@ -12,19 +12,31 @@ final class BoldRule implements Rule, ProvidesFirstChar, ProvidesStopChar { private(set) string $firstChar = '*_'; - private(set) string $stopChar = '_*'; + private(set) string $stopChar = '*_'; public function shouldParse(Parser $parser): bool { - if ($parser->comesNext('**', length: 2)) { - return ! $parser->comesNext('*', length: 1, offset: 2); + $stopToken = $parser->lookaheadUntil('*_')[0] ?? null; + + if (! $stopToken) { + return false; + } + + $lookahead = $parser->lookaheadUntil($stopToken, $stopToken, $stopToken, $stopToken); + + if (count($lookahead) !== 4) { + return false; } - if ($parser->comesNext('__', length: 2)) { - return ! $parser->comesNext('_', length: 1, offset: 2); + $content = $lookahead[2]; + + $lastChar = substr($content, strlen($content) - 1, 1); + + if ($lastChar !== $stopToken) { + return false; } - return false; + return true; } public function parse(Parser $parser): Token diff --git a/src/Rules/ItalicRule.php b/src/Rules/ItalicRule.php index 8d08f05..539970b 100644 --- a/src/Rules/ItalicRule.php +++ b/src/Rules/ItalicRule.php @@ -16,15 +16,32 @@ final class ItalicRule implements Rule, ProvidesFirstChar, ProvidesStopChar public function shouldParse(Parser $parser): bool { - if ($parser->comesNext('_', length: 1)) { - return ! $parser->comesNext('_', length: 1, offset: 1); + $stopToken = $parser->lookaheadUntil('*_')[0] ?? null; + + if (! $stopToken) { + return false; + } + + $lookahead = $parser->lookaheadUntil($stopToken, $stopToken); + + if (count($lookahead) !== 2) { + return false; + } + + $end = $lookahead[1]; + + $firstChar = substr($end, 0, 1); + $lastChar = substr($end, strlen($end) - 1, 1); + + if ($firstChar === $stopToken) { + return false; } - if ($parser->comesNext('*', length: 1)) { - return ! $parser->comesNext('*', length: 1, offset: 1); + if ($lastChar !== $stopToken) { + return false; } - return false; + return true; } public function parse(Parser $parser): Token diff --git a/tests/Rules/BoldAndItalicRuleTest.php b/tests/Rules/BoldAndItalicRuleTest.php index 2063170..44fa12d 100644 --- a/tests/Rules/BoldAndItalicRuleTest.php +++ b/tests/Rules/BoldAndItalicRuleTest.php @@ -27,6 +27,38 @@ public function test_triple_underscore_bold_and_italic(): void $this->assertSame('text', $html); } + #[Test] + public function test_single_underscore_double_asterisk(): void + { + $html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('_**text**_'); + + $this->assertSame('text', $html); + } + + #[Test] + public function test_single_asterisk_double_underscore(): void + { + $html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('*__text__*'); + + $this->assertSame('text', $html); + } + + #[Test] + public function test_double_underscore_single_asterisk(): void + { + $html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('__*text*__'); + + $this->assertSame('text', $html); + } + + #[Test] + public function test_double_asterisk_single_underscore(): void + { + $html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('**_text_**'); + + $this->assertSame('text', $html); + } + #[Test] public function test_does_not_lex_double_asterisk(): void { diff --git a/tests/Rules/BoldRuleTest.php b/tests/Rules/BoldRuleTest.php index 9b4b6e1..8a8cddb 100644 --- a/tests/Rules/BoldRuleTest.php +++ b/tests/Rules/BoldRuleTest.php @@ -5,6 +5,9 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; use Tempest\Markdown\Rules\BoldRule; +use Tempest\Markdown\Rules\ItalicRule; +use Tempest\Markdown\Rules\NewLineRule; +use Tempest\Markdown\Rules\ParagraphRule; use Tempest\Markdown\Tests\ParserTestCase; class BoldRuleTest extends ParserTestCase @@ -17,10 +20,18 @@ public function test_lex_double_asterisk(): void $this->assertSame('bold', $html); } + #[Test] + public function test_double_asterisk_must_be_terminated(): void + { + $html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new BoldRule(), new ParagraphRule()])->parse("Hello**world\n\nHi"); + + $this->assertSame("

Hello**world

\n\n

Hi

", $html); + } + #[Test] public function test_lex_asterisk_with_underscore(): void { - $html = (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse('**_bold_**'); + $html = (string) new Parser(highlighter: null, rules: [new BoldRule(), new ItalicRule()])->parse('**_bold_**'); $this->assertSame('bold', $html); } @@ -41,6 +52,14 @@ public function test_lex_double_underscore(): void $this->assertSame('bold', $html); } + #[Test] + public function test_double_underscore_must_be_terminated(): void + { + $html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new BoldRule(), new ParagraphRule()])->parse("Hello__world\n\nHi"); + + $this->assertSame("

Hello__world

\n\n

Hi

", $html); + } + #[Test] public function test_does_not_lex_single_underscore(): void { diff --git a/tests/Rules/ItalicRuleTest.php b/tests/Rules/ItalicRuleTest.php index 0dc1610..b422c30 100644 --- a/tests/Rules/ItalicRuleTest.php +++ b/tests/Rules/ItalicRuleTest.php @@ -5,6 +5,8 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; use Tempest\Markdown\Rules\ItalicRule; +use Tempest\Markdown\Rules\NewLineRule; +use Tempest\Markdown\Rules\ParagraphRule; use Tempest\Markdown\Tests\ParserTestCase; class ItalicRuleTest extends ParserTestCase @@ -17,6 +19,14 @@ public function test_lex_with_underscore(): void $this->assertSame('italic', $html); } + #[Test] + public function test_underscore_must_be_terminated(): void + { + $html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new ItalicRule(), new ParagraphRule()])->parse("Hello_world\n\nHi"); + + $this->assertSame("

Hello_world

\n\n

Hi

", $html); + } + #[Test] public function test_lex_with_asterisk(): void { @@ -24,4 +34,12 @@ public function test_lex_with_asterisk(): void $this->assertSame('italic', $html); } + + #[Test] + public function test_asterisk_must_be_terminated(): void + { + $html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new ItalicRule(), new ParagraphRule()])->parse("Hello*world\n\nHi"); + + $this->assertSame("

Hello*world

\n\n

Hi

", $html); + } }