From 8eb950e1a33f5239121b4a12220583fc4f578ddc Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Sat, 15 Aug 2026 15:00:15 +0200 Subject: [PATCH 1/2] [BUGFIX] Accept a CodeNode without a language in the highlight filter Rendering a code block whose language is not set aborts the whole run with CodeExtension::highlight(): Argument #3 ($language) must be of type string, null given `CodeNode::getLanguage()` is nullable and `body/code/highlighted-code.html.twig` passes it straight into the filter, so the filter has to accept null. Its signature already carries the intended fallback, `string $language = 'text'`, but a default cannot apply to an argument that is passed explicitly. Accept `string|null` and fall back to `text`. Reproduced with a `literalinclude` without the `:language:` option, which is the shortest way to reach a CodeNode with no language; `code-block` never does, because it always sets either the given language or the configured default. Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_015QXXkquh2eQNBiTYA39Wss Signed-off-by: Sebastian Mendel --- .../src/Code/Twig/CodeExtension.php | 11 +++- .../tests/unit/Twig/CodeExtensionTest.php | 57 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 packages/guides-code/tests/unit/Twig/CodeExtensionTest.php diff --git a/packages/guides-code/src/Code/Twig/CodeExtension.php b/packages/guides-code/src/Code/Twig/CodeExtension.php index 45ae0f461..84ef4dcba 100644 --- a/packages/guides-code/src/Code/Twig/CodeExtension.php +++ b/packages/guides-code/src/Code/Twig/CodeExtension.php @@ -36,14 +36,19 @@ public function getFilters(): array ]; } - /** @param array $context */ - public function highlight(array $context, string $code, string $language = 'text'): string + /** + * `CodeNode::getLanguage()` is nullable, and templates pass it straight into this filter, so null has to be + * accepted here and mean the same as an omitted language. + * + * @param array $context + */ + public function highlight(array $context, string $code, string|null $language = null): string { $debugInformation = $context['debugInformation'] ?? []; if (!is_array($debugInformation)) { $debugInformation = []; } - return ($this->highlighter)($language, $code, $debugInformation)->code; + return ($this->highlighter)($language ?? 'text', $code, $debugInformation)->code; } } diff --git a/packages/guides-code/tests/unit/Twig/CodeExtensionTest.php b/packages/guides-code/tests/unit/Twig/CodeExtensionTest.php new file mode 100644 index 000000000..e7584b6c4 --- /dev/null +++ b/packages/guides-code/tests/unit/Twig/CodeExtensionTest.php @@ -0,0 +1,57 @@ +extension = new CodeExtension(new HighlightPhpHighlighter(new HighlightPHP(), new NullLogger())); + } + + public function testItHighlightsCodeWithoutALanguageAsPlainText(): void + { + // A highlighter treats this differently per language: as "text" the quoted part stays plain, in a + // programming language it becomes a string token. Plain input would be returned unchanged by every + // language and could therefore not tell the fallback apart from any other one. + $code = ' & "b"'; + + self::assertSame( + $this->extension->highlight([], $code, 'text'), + $this->extension->highlight([], $code, null), + 'A CodeNode without a language must be rendered like an explicit "text" language', + ); + + self::assertNotSame( + $this->extension->highlight([], $code, 'php'), + $this->extension->highlight([], $code, null), + 'The fixture must be able to tell the "text" fallback apart from another language', + ); + } + + public function testItHighlightsCodeWithALanguage(): void + { + self::assertStringContainsString( + 'hljs-keyword', + $this->extension->highlight([], ' Date: Wed, 19 Aug 2026 14:12:58 +0200 Subject: [PATCH 2/2] [BUGFIX] Render a CodeNode without a language as language-text The HTML template wrote class="language-{{ node.language }}" without a fallback, so a CodeNode with a null language rendered class="language-" while the highlight filter rendered its content as text. Align the class attribute with that fallback. Only null is mapped, matching the filter: an empty-string language keeps rendering class="language-" as before. The integration test drives the reported reproduction, a literalinclude without :language:, through the CLI with the Code extension enabled, which is where the TypeError was raised. Markdown fenced code blocks without an info string also carry a null language; their expectation changes from language- to language-text. Signed-off-by: Sebastian Mendel --- .../template/html/body/code.html.twig | 2 +- .../expected/index.html | 18 ++++++++++++++++++ .../input/_code/_Example.php | 11 +++++++++++ .../input/guides.xml | 7 +++++++ .../input/index.rst | 4 ++++ .../tests/markdown/code-md/expected/index.html | 4 ++-- 6 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/tests/code/literalinclude-without-language/expected/index.html create mode 100644 tests/Integration/tests/code/literalinclude-without-language/input/_code/_Example.php create mode 100644 tests/Integration/tests/code/literalinclude-without-language/input/guides.xml create mode 100644 tests/Integration/tests/code/literalinclude-without-language/input/index.rst diff --git a/packages/guides/resources/template/html/body/code.html.twig b/packages/guides/resources/template/html/body/code.html.twig index c5efcaa77..53f5eb502 100644 --- a/packages/guides/resources/template/html/body/code.html.twig +++ b/packages/guides/resources/template/html/body/code.html.twig @@ -7,7 +7,7 @@ {{ renderNode(node.caption) }} {%- endif -%} - {%- include "body/code/highlighted-code.html.twig" -%} diff --git a/tests/Integration/tests/code/literalinclude-without-language/expected/index.html b/tests/Integration/tests/code/literalinclude-without-language/expected/index.html new file mode 100644 index 000000000..e3207a7c5 --- /dev/null +++ b/tests/Integration/tests/code/literalinclude-without-language/expected/index.html @@ -0,0 +1,18 @@ + +
+

index

+
<?php
+
+declare(strict_types=1);
+
+class Example
+{
+    public function test(): string
+    {
+        return 'this is a test';
+    }
+}
+
+ +
+ diff --git a/tests/Integration/tests/code/literalinclude-without-language/input/_code/_Example.php b/tests/Integration/tests/code/literalinclude-without-language/input/_code/_Example.php new file mode 100644 index 000000000..ee23d326b --- /dev/null +++ b/tests/Integration/tests/code/literalinclude-without-language/input/_code/_Example.php @@ -0,0 +1,11 @@ + + + + + diff --git a/tests/Integration/tests/code/literalinclude-without-language/input/index.rst b/tests/Integration/tests/code/literalinclude-without-language/input/index.rst new file mode 100644 index 000000000..f881c44d9 --- /dev/null +++ b/tests/Integration/tests/code/literalinclude-without-language/input/index.rst @@ -0,0 +1,4 @@ +index +===== + +.. literalinclude:: _code/_Example.php diff --git a/tests/Integration/tests/markdown/code-md/expected/index.html b/tests/Integration/tests/markdown/code-md/expected/index.html index 14e6691d1..93182a726 100644 --- a/tests/Integration/tests/markdown/code-md/expected/index.html +++ b/tests/Integration/tests/markdown/code-md/expected/index.html @@ -2,7 +2,7 @@

Markdown with Code Blocks

-
<html>
+            
<html>
   <head>
   </head>
 </html>
@@ -10,7 +10,7 @@ 

Markdown with Code Blocks

Fenced Code Blocks

-
{
+            
{
   "firstName": "John",
   "lastName": "Smith",
   "age": 25