From 66906a1ffda72332807595057b7324329fcf4f46 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Tue, 15 Sep 2026 00:50:48 +0200 Subject: [PATCH] feat: make generated heading ids optional --- src/Rules/HeadingRule.php | 12 +++++++++++- tests/Rules/HeadingRuleTest.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Rules/HeadingRule.php b/src/Rules/HeadingRule.php index 2fae918..52f38d6 100644 --- a/src/Rules/HeadingRule.php +++ b/src/Rules/HeadingRule.php @@ -12,6 +12,14 @@ final class HeadingRule implements Rule, ProvidesFirstChar { public string $firstChar = '#'; + public function __construct( + /** + * Whether a heading without an explicit id gets one slugged from its + * content. An id written as `## Title ## id` is kept either way. + */ + public bool $generateIds = true, + ) {} + public function shouldParse(Parser $parser): bool { return $parser->comesNext('#', 1); @@ -37,7 +45,7 @@ public function parse(Parser $parser): Token |> trim(...); $buffer = substr(string: $buffer, offset: 0, length: $idSeparator) |> trim(...); - } else { + } elseif ($this->generateIds) { // No id is specified, we'll slug the heading $id = $buffer |> mb_strtolower(...) @@ -45,6 +53,8 @@ public function parse(Parser $parser): Token preg_replace('/[^\p{L}\p{N}]+/u', '-', $x) ?? '', '-', )); + } else { + $id = null; } return new HeadingToken( diff --git a/tests/Rules/HeadingRuleTest.php b/tests/Rules/HeadingRuleTest.php index 90ff9b3..e1346ee 100644 --- a/tests/Rules/HeadingRuleTest.php +++ b/tests/Rules/HeadingRuleTest.php @@ -80,4 +80,35 @@ public function test_slug_cannot_break_out_of_the_id_attribute(): void $html, ); } + + #[Test] + public function lex_generates_an_id_by_default(): void + { + $html = + (string) new Parser(highlighter: null, rules: [new HeadingRule()])->parse( + '## A heading', + ); + + $this->assertSame('

A heading

', $html); + } + + #[Test] + public function lex_without_generated_ids(): void + { + $html = (string) new Parser(highlighter: null, rules: [ + new HeadingRule(generateIds: false), + ])->parse('## A heading'); + + $this->assertSame('

A heading

', $html); + } + + #[Test] + public function lex_keeps_an_explicit_id_without_generated_ids(): void + { + $html = (string) new Parser(highlighter: null, rules: [ + new HeadingRule(generateIds: false), + ])->parse('## A heading ## custom-id'); + + $this->assertSame('

A heading

', $html); + } }