Skip to content

Commit f3ee04d

Browse files
authored
Merge pull request #14 from tempestphp/terminated_underscore
fix: terminated underscores and asterisks
2 parents ecacdb1 + 7591121 commit f3ee04d

6 files changed

Lines changed: 111 additions & 12 deletions

File tree

mago.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ interface-name = { psr = false }
4646
trait-name = { psr = false }
4747
class-name = { psr = false }
4848
literal-named-argument = { enabled = false } # todo
49+
no-insecure-comparison = { enabled = false }
4950
no-error-control-operator = { enabled = false }
5051
too-many-methods = { enabled = false }
5152
kan-defect = { enabled = false }

src/Rules/BoldRule.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,31 @@
1212
final class BoldRule implements Rule, ProvidesFirstChar, ProvidesStopChar
1313
{
1414
private(set) string $firstChar = '*_';
15-
private(set) string $stopChar = '_*';
15+
private(set) string $stopChar = '*_';
1616

1717
public function shouldParse(Parser $parser): bool
1818
{
19-
if ($parser->comesNext('**', length: 2)) {
20-
return ! $parser->comesNext('*', length: 1, offset: 2);
19+
$stopToken = $parser->lookaheadUntil('*_')[0] ?? null;
20+
21+
if (! $stopToken) {
22+
return false;
23+
}
24+
25+
$lookahead = $parser->lookaheadUntil($stopToken, $stopToken, $stopToken, $stopToken);
26+
27+
if (count($lookahead) !== 4) {
28+
return false;
2129
}
2230

23-
if ($parser->comesNext('__', length: 2)) {
24-
return ! $parser->comesNext('_', length: 1, offset: 2);
31+
$content = $lookahead[2];
32+
33+
$lastChar = substr($content, strlen($content) - 1, 1);
34+
35+
if ($lastChar !== $stopToken) {
36+
return false;
2537
}
2638

27-
return false;
39+
return true;
2840
}
2941

3042
public function parse(Parser $parser): Token

src/Rules/ItalicRule.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,32 @@ final class ItalicRule implements Rule, ProvidesFirstChar, ProvidesStopChar
1616

1717
public function shouldParse(Parser $parser): bool
1818
{
19-
if ($parser->comesNext('_', length: 1)) {
20-
return ! $parser->comesNext('_', length: 1, offset: 1);
19+
$stopToken = $parser->lookaheadUntil('*_')[0] ?? null;
20+
21+
if (! $stopToken) {
22+
return false;
23+
}
24+
25+
$lookahead = $parser->lookaheadUntil($stopToken, $stopToken);
26+
27+
if (count($lookahead) !== 2) {
28+
return false;
29+
}
30+
31+
$end = $lookahead[1];
32+
33+
$firstChar = substr($end, 0, 1);
34+
$lastChar = substr($end, strlen($end) - 1, 1);
35+
36+
if ($firstChar === $stopToken) {
37+
return false;
2138
}
2239

23-
if ($parser->comesNext('*', length: 1)) {
24-
return ! $parser->comesNext('*', length: 1, offset: 1);
40+
if ($lastChar !== $stopToken) {
41+
return false;
2542
}
2643

27-
return false;
44+
return true;
2845
}
2946

3047
public function parse(Parser $parser): Token

tests/Rules/BoldAndItalicRuleTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,38 @@ public function test_triple_underscore_bold_and_italic(): void
2727
$this->assertSame('<strong><em>text</em></strong>', $html);
2828
}
2929

30+
#[Test]
31+
public function test_single_underscore_double_asterisk(): void
32+
{
33+
$html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('_**text**_');
34+
35+
$this->assertSame('<em><strong>text</strong></em>', $html);
36+
}
37+
38+
#[Test]
39+
public function test_single_asterisk_double_underscore(): void
40+
{
41+
$html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('*__text__*');
42+
43+
$this->assertSame('<em><strong>text</strong></em>', $html);
44+
}
45+
46+
#[Test]
47+
public function test_double_underscore_single_asterisk(): void
48+
{
49+
$html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('__*text*__');
50+
51+
$this->assertSame('<strong><em>text</em></strong>', $html);
52+
}
53+
54+
#[Test]
55+
public function test_double_asterisk_single_underscore(): void
56+
{
57+
$html = (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule(), new BoldRule(), new ItalicRule()])->parse('**_text_**');
58+
59+
$this->assertSame('<strong><em>text</em></strong>', $html);
60+
}
61+
3062
#[Test]
3163
public function test_does_not_lex_double_asterisk(): void
3264
{

tests/Rules/BoldRuleTest.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
use PHPUnit\Framework\Attributes\Test;
66
use Tempest\Markdown\Parser;
77
use Tempest\Markdown\Rules\BoldRule;
8+
use Tempest\Markdown\Rules\ItalicRule;
9+
use Tempest\Markdown\Rules\NewLineRule;
10+
use Tempest\Markdown\Rules\ParagraphRule;
811
use Tempest\Markdown\Tests\ParserTestCase;
912

1013
class BoldRuleTest extends ParserTestCase
@@ -17,10 +20,18 @@ public function test_lex_double_asterisk(): void
1720
$this->assertSame('<strong>bold</strong>', $html);
1821
}
1922

23+
#[Test]
24+
public function test_double_asterisk_must_be_terminated(): void
25+
{
26+
$html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new BoldRule(), new ParagraphRule()])->parse("Hello**world\n\nHi");
27+
28+
$this->assertSame("<p>Hello**world</p>\n\n<p>Hi</p>", $html);
29+
}
30+
2031
#[Test]
2132
public function test_lex_asterisk_with_underscore(): void
2233
{
23-
$html = (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse('**_bold_**');
34+
$html = (string) new Parser(highlighter: null, rules: [new BoldRule(), new ItalicRule()])->parse('**_bold_**');
2435

2536
$this->assertSame('<strong><em>bold</em></strong>', $html);
2637
}
@@ -41,6 +52,14 @@ public function test_lex_double_underscore(): void
4152
$this->assertSame('<strong>bold</strong>', $html);
4253
}
4354

55+
#[Test]
56+
public function test_double_underscore_must_be_terminated(): void
57+
{
58+
$html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new BoldRule(), new ParagraphRule()])->parse("Hello__world\n\nHi");
59+
60+
$this->assertSame("<p>Hello__world</p>\n\n<p>Hi</p>", $html);
61+
}
62+
4463
#[Test]
4564
public function test_does_not_lex_single_underscore(): void
4665
{

tests/Rules/ItalicRuleTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use PHPUnit\Framework\Attributes\Test;
66
use Tempest\Markdown\Parser;
77
use Tempest\Markdown\Rules\ItalicRule;
8+
use Tempest\Markdown\Rules\NewLineRule;
9+
use Tempest\Markdown\Rules\ParagraphRule;
810
use Tempest\Markdown\Tests\ParserTestCase;
911

1012
class ItalicRuleTest extends ParserTestCase
@@ -17,11 +19,27 @@ public function test_lex_with_underscore(): void
1719
$this->assertSame('<em>italic</em>', $html);
1820
}
1921

22+
#[Test]
23+
public function test_underscore_must_be_terminated(): void
24+
{
25+
$html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new ItalicRule(), new ParagraphRule()])->parse("Hello_world\n\nHi");
26+
27+
$this->assertSame("<p>Hello_world</p>\n\n<p>Hi</p>", $html);
28+
}
29+
2030
#[Test]
2131
public function test_lex_with_asterisk(): void
2232
{
2333
$html = (string) new Parser(highlighter: null, rules: [new ItalicRule()])->parse('*italic*');
2434

2535
$this->assertSame('<em>italic</em>', $html);
2636
}
37+
38+
#[Test]
39+
public function test_asterisk_must_be_terminated(): void
40+
{
41+
$html = (string) new Parser(highlighter: null, rules: [new NewLineRule(), new ItalicRule(), new ParagraphRule()])->parse("Hello*world\n\nHi");
42+
43+
$this->assertSame("<p>Hello*world</p>\n\n<p>Hi</p>", $html);
44+
}
2745
}

0 commit comments

Comments
 (0)