Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions src/Rules/StrikethroughRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,55 @@ public function parse(Parser $parser): Token
{
$opening = $parser->consumeWhile('~');

if (strpos($parser->content, '~', $parser->position) === false) {
$closing = $this->closingPosition($parser, strlen($opening));

if ($closing === null) {
return new TextToken($opening);
}

$buffer = $parser->consumeUntil('~');
$parser->consumeWhile('~');
$buffer = $parser->consume($closing - $parser->position);

$parser->consume(strlen($opening));

return new StrikethroughToken($buffer);
}

/**
* The position of the run that closes $length tildes, or null when there
* is none. A run of more than two tildes never delimits strikethrough, an
* opening run may not be followed by whitespace, and a closing run may not
* be preceded by it.
*
* @see https://github.github.com/gfm/#strikethrough-extension-
*/
private function closingPosition(Parser $parser, int $length): ?int
{
if ($length > 2) {
return null;
}

$next = $parser->current;

if ($next === null || str_contains(Parser::WHITESPACE, $next)) {
return null;
}

$content = $parser->content;
$position = $parser->position;

while (($position = strpos($content, '~', $position)) !== false) {
$run = strspn($content, '~', $position);

if (
$run === $length
&& ! str_contains(Parser::WHITESPACE, $content[$position - 1])
) {
return $position;
}

$position += $run;
}

return null;
}
}
46 changes: 46 additions & 0 deletions tests/Rules/StrikethroughRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tempest\Markdown\Tests\Rules;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tempest\Markdown\Markdown;
use Tempest\Markdown\Parser;
Expand Down Expand Up @@ -57,6 +58,51 @@ public function unmatched_tildes_remain_literal(): void
);
}

#[Test]
#[DataProvider('provideFlanking')]
public function delimiters_follow_the_flanking_rules(
string $markdown,
string $expected,
): void {
$this->assertSame(
$expected,
new Markdown(highlighter: null)->parse($markdown)->html,
);
}

public static function provideFlanking(): iterable
{
yield 'one tilde' => ['~struck~', '<p><s>struck</s></p>'];
yield 'two tildes' => ['~~struck~~', '<p><s>struck</s></p>'];
yield 'inside a word' => ['a~struck~b', '<p>a<s>struck</s>b</p>'];

// The opening run may not be followed by whitespace, and the closing
// run may not be preceded by it.
yield 'space after the opening run' => [
'~ struck ~',
'<p>~ struck ~</p>',
];
yield 'space before the closing run' => [
'~11km / ~750m',
'<p>~11km / ~750m</p>',
];
yield 'approximations in a list item' => [
"- Distance : ~120 km\n- Dénivelé : ~6 700 m D+ / ~7 300 m D-",
'<ul><li>Distance : ~120 km</li>'
. '<li>Dénivelé : ~6 700 m D+ / ~7 300 m D-</li></ul>',
];

yield 'approximations in a table cell' => [
"| a |\n| - |\n| ~120 et ~700 |",
'<table><thead><tr><th>a</th></tr></thead>'
. '<tbody><tr><td>~120 et ~700</td></tr></tbody></table>',
];

// Opening and closing runs have to be the same length.
yield 'one tilde closed by two' => ['~struck~~', '<p>~struck~~</p>'];
yield 'two tildes closed by one' => ['~~struck~', '<p>~~struck~</p>'];
}

#[Test]
public function test_unmatched_run_is_consumed_as_one_literal_token(): void
{
Expand Down
Loading