From d3110911a1b706d62148212df96fe99bb16dbc67 Mon Sep 17 00:00:00 2001 From: ybai08 <271969568+ybai08@users.noreply.github.com> Date: Sun, 18 Oct 2026 04:45:45 -0500 Subject: [PATCH] Allow info string of fenced code to start with the other fence character An opening fence like "```~/.bashrc" or "~~~`info`" was not recognized, because backticks and tildes after the first character were counted as part of the fence. The fence is only the run of the first character, so the info string can start directly after it with the other character. --- .../internal/FencedCodeBlockParser.java | 23 ++++---------- .../test/FencedCodeBlockParserTest.java | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 042e08f6b..87ff056b8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -94,30 +94,19 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar // spec: A code fence is a sequence of at least three consecutive backtick characters (`) or // tildes (~). (Tildes and backticks cannot be mixed.) private static FencedCodeBlockParser checkOpener(CharSequence line, int index, int indent) { - int backticks = 0; - int tildes = 0; + // The fence is only the run of the first character, the info string can start directly + // after it with the other character (e.g. "```~" or "~~~`"). int length = line.length(); - loop: - for (int i = index; i < length; i++) { - switch (line.charAt(i)) { - case '`': - backticks++; - break; - case '~': - tildes++; - break; - default: - break loop; - } - } - if (backticks >= 3 && tildes == 0) { + int backticks = Characters.skip('`', line, index, length) - index; + int tildes = Characters.skip('~', line, index, length) - index; + if (backticks >= 3) { // spec: If the info string comes after a backtick fence, it may not contain any // backtick characters. if (Characters.find('`', line, index + backticks) != -1) { return null; } return new FencedCodeBlockParser('`', backticks, indent); - } else if (tildes >= 3 && backticks == 0) { + } else if (tildes >= 3) { // spec: Info strings for tilde code blocks can contain backticks and tildes return new FencedCodeBlockParser('~', tildes, indent); } else { diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index 1ccd8a8db..02fec0fa7 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -27,6 +27,36 @@ public void backtickInfo() { assertThat(codeBlock.getLiteral()).isEqualTo("code\n"); } + @Test + public void backtickInfoCanStartWithTilde() { + var s = + """ + ```~/.bashrc + code + ``` + """; + var document = PARSER.parse(s); + FencedCodeBlock codeBlock = (FencedCodeBlock) document.getFirstChild(); + assertThat(codeBlock.getFenceCharacter()).isEqualTo("`"); + assertThat(codeBlock.getOpeningFenceLength()).isEqualTo(3); + assertThat(codeBlock.getInfo()).isEqualTo("~/.bashrc"); + assertThat(codeBlock.getLiteral()).isEqualTo("code\n"); + } + + @Test + public void tildeInfoCanStartWithBacktick() { + assertRendering( + """ + ~~~`info` + code + ~~~ + """, + """ +
code
+
+ """);
+ }
+
@Test
public void backtickInfoDoesntAllowBacktick() {
assertRendering(