diff --git a/core/modules/parsers/wikiparser/rules/codeblock.js b/core/modules/parsers/wikiparser/rules/codeblock.js index 1171704b37..744c2ff46f 100644 --- a/core/modules/parsers/wikiparser/rules/codeblock.js +++ b/core/modules/parsers/wikiparser/rules/codeblock.js @@ -25,7 +25,7 @@ exports.init = function(parser) { }; exports.parse = function() { - var reEnd = /(\r?\n```$)/mg; + var reEnd = /(^|\r?\n)```$/mg; var languageStart = this.parser.pos + 3, languageEnd = languageStart + this.match[1].length; // Move past the match diff --git a/editions/test/tiddlers/tests/test-codeblock-parser.js b/editions/test/tiddlers/tests/test-codeblock-parser.js new file mode 100644 index 0000000000..905dcf799b --- /dev/null +++ b/editions/test/tiddlers/tests/test-codeblock-parser.js @@ -0,0 +1,69 @@ +/*\ +title: test-codeblock-parser.js +type: application/javascript +tags: [[$:/tags/test-spec]] + +Tests the codeblock wikitext rule (#9047): an empty code block must parse +cleanly, and a closing fence only counts when it stands alone on its line. + +\*/ +"use strict"; + +describe("codeblock parser tests (#9047)", function() { + + var wiki = new $tw.Wiki(); + + function parse(text) { + return wiki.parseText("text/vnd.tiddlywiki",text).tree; + } + + it("parses an empty code block with a language", function() { + // Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```bash\n```").tree + // Expected: one codeblock node, code "", language "bash". + var tree = parse("```bash\n```"); + expect(tree.length).toBe(1); + expect(tree[0].type).toBe("codeblock"); + expect(tree[0].attributes.code.value).toBe(""); + expect(tree[0].attributes.language.value).toBe("bash"); + }); + + it("handles CRLF line endings around both fences", function() { + // Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```bash\r\nfoo\r\n```").tree + // Expected: one codeblock node with code "foo"; neither the CRLF after the + // opening fence nor the one before the closing fence is part of the code. + var tree = parse("```bash\r\nfoo\r\n```"); + expect(tree.length).toBe(1); + expect(tree[0].type).toBe("codeblock"); + expect(tree[0].attributes.code.value).toBe("foo"); + }); + + it("keeps the content of a block and drops the delimiting newlines", function() { + // Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```\nfoo\n```").tree + // Expected: one codeblock node with code "foo"; the newlines around the + // delimiter lines are not part of the code. + var tree = parse("```\nfoo\n```"); + expect(tree.length).toBe(1); + expect(tree[0].type).toBe("codeblock"); + expect(tree[0].attributes.code.value).toBe("foo"); + }); + + it("only closes the block when the fence stands alone on its line", function() { + // A content line ending in ``` is code, not a closing fence, e.g. nested + // markdown fences or template literals quoted inside a code block. + // Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```\nabc```\ndef\n```").tree + // Expected: one codeblock containing "abc```\ndef"; nothing leaks out as wikitext. + var tree = parse("```\nabc```\ndef\n```"); + expect(tree.length).toBe(1); + expect(tree[0].type).toBe("codeblock"); + expect(tree[0].attributes.code.value).toBe("abc```\ndef"); + }); + + it("swallows the rest of the tiddler when no closing fence exists", function() { + // Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```bash\nfoo").tree + // Expected: one codeblock node with code "foo", extending to the end of the text. + var tree = parse("```bash\nfoo"); + expect(tree.length).toBe(1); + expect(tree[0].type).toBe("codeblock"); + expect(tree[0].attributes.code.value).toBe("foo"); + }); +}); diff --git a/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9739-empty-codeblock.tid b/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9739-empty-codeblock.tid new file mode 100644 index 0000000000..83a5ae79b4 --- /dev/null +++ b/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9739-empty-codeblock.tid @@ -0,0 +1,13 @@ +change-category: hackability +change-type: bugfix +created: 20260711181412000 +description: Empty code blocks parse cleanly; a closing fence only counts alone on its line +github-contributors: pmario +github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9739 +release: 5.5.0 +tags: $:/tags/ChangeNote +title: $:/changenotes/5.5.0/#9739 +type: text/vnd.tiddlywiki + +* An empty code block such as ```` ```bash ```` directly followed by the closing fence parses as an empty codeblock instead of swallowing the rest of the tiddler (fixes [[Issue #9047|https://github.com/TiddlyWiki/TiddlyWiki5/issues/9047]]) +* A closing fence only ends the block when it stands alone on its line, so code lines ending in three backticks stay inside the block