From 2a56ee8ca3a21405cf0ec22d664b08d4e6cd2549 Mon Sep 17 00:00:00 2001 From: mck09 Date: Sun, 30 Aug 2026 13:14:50 +0200 Subject: [PATCH 1/2] fix: do not add a newline to an empty code block Renderer.code appended a newline unconditionally, so a code block with no content rendered as
\n
instead of
. This fixes CommonMark examples 126, 130, 144 and 237. The spec suite did not catch it because htmlIsEqual leaves html-differ's ignoreWhitespaces at its default of true, and whitespace inside pre/code is significant, so all four were already reported as passing. --- src/Renderer.ts | 3 ++- test/unit/marked.test.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Renderer.ts b/src/Renderer.ts index 890ac2c4ec..c0a308bc36 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -25,7 +25,8 @@ export class _Renderer { code({ text, lang, escaped }: Tokens.Code): RendererOutput { const langString = (lang || '').match(other.notSpaceStart)?.[0]; - const code = text.replace(other.endingNewline, '') + '\n'; + // An empty code block has no content, so it must not gain a newline. + const code = text ? text.replace(other.endingNewline, '') + '\n' : ''; if (!langString) { return '
'
diff --git a/test/unit/marked.test.js b/test/unit/marked.test.js
index 5d1449b4aa..f1b4ab4c07 100644
--- a/test/unit/marked.test.js
+++ b/test/unit/marked.test.js
@@ -22,6 +22,27 @@ describe('marked unit', () => {
     });
   });
 
+  describe('empty code block', () => {
+    it('should not add a newline to an unclosed empty fence', () => {
+      assert.strictEqual(marked.parse('```\n'), '
\n'); + }); + + it('should not add a newline to a closed empty fence', () => { + assert.strictEqual(marked.parse('```\n```\n'), '
\n'); + }); + + it('should not add a newline to an empty fence with a language', () => { + assert.strictEqual( + marked.parse('````;\n````\n'), + '
\n', + ); + }); + + it('should keep the content of a non-empty code block', () => { + assert.strictEqual(marked.parse('```\nx\n```\n'), '
x\n
\n'); + }); + }); + describe('changeDefaults', () => { it('should change global defaults', async() => { const { defaults, setOptions } = await import('../../lib/marked.esm.js'); From 474ae0556fa75a508ba16a3d79fca902333e56ba Mon Sep 17 00:00:00 2001 From: mck09 Date: Thu, 3 Sep 2026 04:41:48 +0200 Subject: [PATCH 2/2] test: move the empty code block tests to test/specs/new Uses renderExact so the fixture compares exactly, which htmlIsEqual does not, and keeps the assertions out of marked.test.js. --- test/specs/new/empty_code_block.html | 8 ++++++++ test/specs/new/empty_code_block.md | 20 ++++++++++++++++++++ test/unit/marked.test.js | 21 --------------------- 3 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 test/specs/new/empty_code_block.html create mode 100644 test/specs/new/empty_code_block.md diff --git a/test/specs/new/empty_code_block.html b/test/specs/new/empty_code_block.html new file mode 100644 index 0000000000..2dc57fc61e --- /dev/null +++ b/test/specs/new/empty_code_block.html @@ -0,0 +1,8 @@ +
+

sep

+
+

sep

+
x
+
+

sep

+
diff --git a/test/specs/new/empty_code_block.md b/test/specs/new/empty_code_block.md new file mode 100644 index 0000000000..11b7d8b466 --- /dev/null +++ b/test/specs/new/empty_code_block.md @@ -0,0 +1,20 @@ +--- +renderExact: true +--- +``` +``` + +sep + +````; +```` + +sep + +``` +x +``` + +sep + +``` diff --git a/test/unit/marked.test.js b/test/unit/marked.test.js index f1b4ab4c07..5d1449b4aa 100644 --- a/test/unit/marked.test.js +++ b/test/unit/marked.test.js @@ -22,27 +22,6 @@ describe('marked unit', () => { }); }); - describe('empty code block', () => { - it('should not add a newline to an unclosed empty fence', () => { - assert.strictEqual(marked.parse('```\n'), '
\n'); - }); - - it('should not add a newline to a closed empty fence', () => { - assert.strictEqual(marked.parse('```\n```\n'), '
\n'); - }); - - it('should not add a newline to an empty fence with a language', () => { - assert.strictEqual( - marked.parse('````;\n````\n'), - '
\n', - ); - }); - - it('should keep the content of a non-empty code block', () => { - assert.strictEqual(marked.parse('```\nx\n```\n'), '
x\n
\n'); - }); - }); - describe('changeDefaults', () => { it('should change global defaults', async() => { const { defaults, setOptions } = await import('../../lib/marked.esm.js');