From f8f7684fb67d92d679ff87da4a32e89c4588fdd3 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 19:24:09 +1100 Subject: [PATCH 1/9] add node depth limit --- .../org/commonmark/node/AbstractVisitor.java | 23 ++++++- .../commonmark/test/AbstractVisitorTest.java | 64 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index 7edd635d7..b7389ceb9 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -7,6 +7,19 @@ * call {@link #visitChildren}. */ public abstract class AbstractVisitor implements Visitor { + private final int maxDepth; + private int currentDepth; + + public AbstractVisitor() { + this(Integer.MAX_VALUE); + } + + protected AbstractVisitor(int maxDepth) { + if (maxDepth < 0) { + throw new IllegalArgumentException("maxDepth must be >= 0"); + } + this.maxDepth = maxDepth; + } @Override public void visit(BlockQuote blockQuote) { @@ -129,12 +142,20 @@ public void visit(CustomNode customNode) { * @param parent the parent node whose children should be visited */ protected void visitChildren(Node parent) { + if (currentDepth >= maxDepth) { + return; + } Node node = parent.getFirstChild(); while (node != null) { // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no // node after visiting it. So get the next node before visiting. Node next = node.getNext(); - node.accept(this); + currentDepth++; + try { + node.accept(this); + } finally { + currentDepth--; + } node = next; } } diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index edb6936f4..554f9c25e 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -3,9 +3,38 @@ import org.commonmark.node.*; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AbstractVisitorTest { + @Test + public void maxDepthMustBeZeroOrGreater() { + assertThatThrownBy(() -> new RecordingVisitor(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void maxDepthZeroVisitsOnlyRoot() { + var paragraph = paragraphTree(); + var visitor = new RecordingVisitor(0); + + paragraph.accept(visitor); + + assertThat(visitor.visited).containsExactly("paragraph"); + } + + @Test + public void maxDepthOneVisitsDirectChildrenButNotGrandchildren() { + var paragraph = paragraphTree(); + var visitor = new RecordingVisitor(1); + + paragraph.accept(visitor); + + assertThat(visitor.visited).containsExactly("paragraph", "emphasis", "text:tail"); + } @Test public void replacingNodeInVisitorShouldNotDestroyVisitOrder() { @@ -34,4 +63,39 @@ private static void assertCode(String expectedLiteral, Node node) { Code code = (Code) node; assertThat(code.getLiteral()).isEqualTo(expectedLiteral); } + + private static Paragraph paragraphTree() { + var paragraph = new Paragraph(); + var emphasis = new Emphasis(); + emphasis.appendChild(new Text("nested")); + paragraph.appendChild(emphasis); + paragraph.appendChild(new Text("tail")); + return paragraph; + } + + private static final class RecordingVisitor extends AbstractVisitor { + private final List visited = new ArrayList<>(); + + private RecordingVisitor(int maxDepth) { + super(maxDepth); + } + + @Override + public void visit(Paragraph paragraph) { + visited.add("paragraph"); + super.visit(paragraph); + } + + @Override + public void visit(Emphasis emphasis) { + visited.add("emphasis"); + super.visit(emphasis); + } + + @Override + public void visit(Text text) { + visited.add("text:" + text.getLiteral()); + super.visit(text); + } + } } From 50d2c368f8f4225da809697f371c7c4ea9ee05c2 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:21:55 +1100 Subject: [PATCH 2/9] abort deep nodes early --- .../commonmark/internal/DocumentParser.java | 8 +- .../java/org/commonmark/parser/Parser.java | 26 ++++- .../java/org/commonmark/test/ParserTest.java | 102 ++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d935f8d27..2b9db45cc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -76,6 +76,7 @@ public class DocumentParser implements ParserState { private final List linkProcessors; private final Set linkMarkers; private final IncludeSourceSpans includeSourceSpans; + private final int maxOpenBlockParsers; private final DocumentBlockParser documentBlockParser; private final Definitions definitions = new Definitions(); @@ -84,7 +85,8 @@ public class DocumentParser implements ParserState { public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, List inlineContentParserFactories, List delimiterProcessors, - List linkProcessors, Set linkMarkers, IncludeSourceSpans includeSourceSpans) { + List linkProcessors, Set linkMarkers, + IncludeSourceSpans includeSourceSpans, int maxOpenBlockParsers) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.inlineContentParserFactories = inlineContentParserFactories; @@ -92,6 +94,7 @@ public DocumentParser(List blockParserFactories, InlineParse this.linkProcessors = linkProcessors; this.linkMarkers = linkMarkers; this.includeSourceSpans = includeSourceSpans; + this.maxOpenBlockParsers = maxOpenBlockParsers; this.documentBlockParser = new DocumentBlockParser(); activateBlockParser(new OpenBlockParser(documentBlockParser, 0)); @@ -461,6 +464,9 @@ private void addSourceSpans() { } private BlockStartImpl findBlockStart(BlockParser blockParser) { + if (openBlockParsers.size() - 1 >= maxOpenBlockParsers) { + return null; + } MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); for (BlockParserFactory blockParserFactory : blockParserFactories) { BlockStart result = blockParserFactory.tryStart(this, matchedBlockParser); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index b98d0581f..9e90e906b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -37,6 +37,7 @@ public class Parser { private final InlineParserFactory inlineParserFactory; private final List postProcessors; private final IncludeSourceSpans includeSourceSpans; + private final int maxOpenBlockParsers; private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); @@ -47,6 +48,7 @@ private Parser(Builder builder) { this.linkProcessors = builder.linkProcessors; this.linkMarkers = builder.linkMarkers; this.includeSourceSpans = builder.includeSourceSpans; + this.maxOpenBlockParsers = builder.maxOpenBlockParsers; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. @@ -106,7 +108,7 @@ public Node parseReader(Reader input) throws IOException { private DocumentParser createDocumentParser() { return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, - delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans); + delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans, maxOpenBlockParsers); } private Node postProcess(Node document) { @@ -129,6 +131,7 @@ public static class Builder { private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; + private int maxOpenBlockParsers = Integer.MAX_VALUE; /** * @return the configured {@link Parser} @@ -200,6 +203,27 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { return this; } + /** + * Limit how many non-document block parsers may be open at once while parsing. + *

+ * Once the limit is reached, additional block starts are treated as plain text instead of + * creating deeper nested block structure. + *

+ * The document root parser is not counted. The default is unlimited, so callers that keep + * using {@code Parser.builder().build()} preserve current behavior. + * + * @param maxOpenBlockParsers maximum number of open non-document block parsers, must be + * zero or greater + * @return {@code this} + */ + public Builder maxOpenBlockParsers(int maxOpenBlockParsers) { + if (maxOpenBlockParsers < 0) { + throw new IllegalArgumentException("maxOpenBlockParsers must be >= 0"); + } + this.maxOpenBlockParsers = maxOpenBlockParsers; + return this; + } + /** * Add a custom block parser factory. *

diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index c119b5e2d..6164759ec 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -4,6 +4,7 @@ import org.commonmark.parser.*; import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.TestResources; import org.junit.jupiter.api.Test; @@ -135,6 +136,76 @@ public void threading() throws Exception { } } + @Test + public void maxOpenBlockParsersMustBeZeroOrGreater() { + assertThatThrownBy(() -> + Parser.builder().maxOpenBlockParsers(-1)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void maxOpenBlockParsersIsOptIn() { + var parser = Parser.builder().build(); + + var document = parser.parse(alternatingNestedList(9)); + + assertThat(renderText(deepestStructuredParagraph(document, 9))).isEqualTo("level9"); + } + + @Test + public void maxOpenBlockParsersPreservesSevenLogicalListLevelsAtSeventeenBlocks() { + var parser = Parser.builder().maxOpenBlockParsers(17).build(); + + var document = parser.parse(alternatingNestedList(7)); + + assertThat(renderText(deepestStructuredParagraph(document, 7))).isEqualTo("level7"); + } + + @Test + public void maxOpenBlockParsersPreservesEightLogicalListLevelsAtSeventeenBlocks() { + var parser = Parser.builder().maxOpenBlockParsers(17).build(); + + var document = parser.parse(alternatingNestedList(8)); + + assertThat(renderText(deepestStructuredParagraph(document, 8))).isEqualTo("level8"); + } + + @Test + public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() { + var parser = Parser.builder().maxOpenBlockParsers(17).build(); + + var document = parser.parse(alternatingNestedList(9)); + var deepestParagraph = deepestStructuredParagraph(document, 8); + + assertThat(renderText(deepestParagraph)).isEqualTo("level8\n- level9"); + assertThat(deepestParagraph.getNext()).isNull(); + } + + @Test + public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { + var parser = Parser.builder().maxOpenBlockParsers(5).build(); + + var document = parser.parse(String.join("\n", + "- level1", + " > level2", + " > > level3", + " > > > level4")); + + var listBlock = document.getFirstChild(); + assertThat(listBlock).isInstanceOf(BulletList.class); + + var listItem = listBlock.getFirstChild(); + var blockQuote1 = listItem.getLastChild(); + assertThat(blockQuote1).isInstanceOf(BlockQuote.class); + + var blockQuote2 = blockQuote1.getLastChild(); + assertThat(blockQuote2).isInstanceOf(BlockQuote.class); + + var deepestParagraph = blockQuote2.getLastChild(); + assertThat(deepestParagraph).isInstanceOf(Paragraph.class); + assertThat(renderText(deepestParagraph)).isEqualTo("level3\n> level4"); + assertThat(deepestParagraph.getNext()).isNull(); + } + private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); @@ -142,4 +213,35 @@ private String firstText(Node n) { } return ((Text) n).getLiteral(); } + + private Paragraph deepestStructuredParagraph(Node document, int levels) { + Node node = document.getFirstChild(); + for (int level = 1; level <= levels; level++) { + assertThat(node).isInstanceOf(ListBlock.class); + var listItem = node.getFirstChild(); + assertThat(listItem).isNotNull(); + if (level == levels) { + assertThat(listItem.getFirstChild()).isInstanceOf(Paragraph.class); + return (Paragraph) listItem.getFirstChild(); + } + node = listItem.getLastChild(); + } + throw new AssertionError("unreachable"); + } + + private String renderText(Node node) { + return TextContentRenderer.builder().build().render(node).trim(); + } + + private String alternatingNestedList(int levels) { + int indent = 0; + var lines = new ArrayList(); + for (int level = 1; level <= levels; level++) { + var ordered = level % 2 == 0; + var marker = ordered ? "1. " : "- "; + lines.add(" ".repeat(indent) + marker + "level" + level); + indent += marker.length(); + } + return String.join("\n", lines); + } } From 7e39836541e97eec661dd2e331b19b45082cbc19 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:23:25 +1100 Subject: [PATCH 3/9] Apply suggestion from @spirit-at-canva --- .../src/main/java/org/commonmark/internal/DocumentParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 2b9db45cc..07d97296b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -464,7 +464,7 @@ private void addSourceSpans() { } private BlockStartImpl findBlockStart(BlockParser blockParser) { - if (openBlockParsers.size() - 1 >= maxOpenBlockParsers) { + if (openBlockParsers.size() > maxOpenBlockParsers) { return null; } MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); From 7a9558b095574202647fb2e7cb061dfbe082e396 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:25:11 +1100 Subject: [PATCH 4/9] Apply suggestion from @spirit-at-canva --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 9e90e906b..dc4decf28 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -210,7 +210,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { * creating deeper nested block structure. *

* The document root parser is not counted. The default is unlimited, so callers that keep - * using {@code Parser.builder().build()} preserve current behavior. + * using {@code Parser.builder().build()} preserve behavior. * * @param maxOpenBlockParsers maximum number of open non-document block parsers, must be * zero or greater From 0c026d5b0062c4edf71d592dce67796c89628ed1 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 12:59:26 +1100 Subject: [PATCH 5/9] remove changes to visitor --- .../org/commonmark/node/AbstractVisitor.java | 23 +--- .../commonmark/test/AbstractVisitorTest.java | 64 ----------- .../java/org/commonmark/test/ParserTest.java | 105 ++++++++++++++++++ 3 files changed, 106 insertions(+), 86 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index b7389ceb9..7edd635d7 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -7,19 +7,6 @@ * call {@link #visitChildren}. */ public abstract class AbstractVisitor implements Visitor { - private final int maxDepth; - private int currentDepth; - - public AbstractVisitor() { - this(Integer.MAX_VALUE); - } - - protected AbstractVisitor(int maxDepth) { - if (maxDepth < 0) { - throw new IllegalArgumentException("maxDepth must be >= 0"); - } - this.maxDepth = maxDepth; - } @Override public void visit(BlockQuote blockQuote) { @@ -142,20 +129,12 @@ public void visit(CustomNode customNode) { * @param parent the parent node whose children should be visited */ protected void visitChildren(Node parent) { - if (currentDepth >= maxDepth) { - return; - } Node node = parent.getFirstChild(); while (node != null) { // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no // node after visiting it. So get the next node before visiting. Node next = node.getNext(); - currentDepth++; - try { - node.accept(this); - } finally { - currentDepth--; - } + node.accept(this); node = next; } } diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index 554f9c25e..edb6936f4 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -3,38 +3,9 @@ import org.commonmark.node.*; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AbstractVisitorTest { - @Test - public void maxDepthMustBeZeroOrGreater() { - assertThatThrownBy(() -> new RecordingVisitor(-1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void maxDepthZeroVisitsOnlyRoot() { - var paragraph = paragraphTree(); - var visitor = new RecordingVisitor(0); - - paragraph.accept(visitor); - - assertThat(visitor.visited).containsExactly("paragraph"); - } - - @Test - public void maxDepthOneVisitsDirectChildrenButNotGrandchildren() { - var paragraph = paragraphTree(); - var visitor = new RecordingVisitor(1); - - paragraph.accept(visitor); - - assertThat(visitor.visited).containsExactly("paragraph", "emphasis", "text:tail"); - } @Test public void replacingNodeInVisitorShouldNotDestroyVisitOrder() { @@ -63,39 +34,4 @@ private static void assertCode(String expectedLiteral, Node node) { Code code = (Code) node; assertThat(code.getLiteral()).isEqualTo(expectedLiteral); } - - private static Paragraph paragraphTree() { - var paragraph = new Paragraph(); - var emphasis = new Emphasis(); - emphasis.appendChild(new Text("nested")); - paragraph.appendChild(emphasis); - paragraph.appendChild(new Text("tail")); - return paragraph; - } - - private static final class RecordingVisitor extends AbstractVisitor { - private final List visited = new ArrayList<>(); - - private RecordingVisitor(int maxDepth) { - super(maxDepth); - } - - @Override - public void visit(Paragraph paragraph) { - visited.add("paragraph"); - super.visit(paragraph); - } - - @Override - public void visit(Emphasis emphasis) { - visited.add("emphasis"); - super.visit(emphasis); - } - - @Override - public void visit(Text text) { - visited.add("text:" + text.getLiteral()); - super.visit(text); - } - } } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 6164759ec..5550df225 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -206,6 +206,55 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { assertThat(deepestParagraph.getNext()).isNull(); } + @Test + public void maxOpenBlockParsersAlignsWithVisitorDepthAtDocumentRoot() { + var parser = Parser.builder().maxOpenBlockParsers(5).build(); + + var document = parser.parse(String.join("\n", + "- level1", + " > level2", + " > > level3", + " > > > level4")); + var listBlock = document.getFirstChild(); + var listItem = listBlock.getFirstChild(); + var blockQuote1 = listItem.getLastChild(); + var blockQuote2 = blockQuote1.getLastChild(); + var deepestParagraph = blockQuote2.getLastChild(); + var depthFiveVisitor = new RecordingVisitor(5); + var unlimitedVisitor = new RecordingVisitor(); + + assertThat(depth(deepestParagraph.getFirstChild())).isEqualTo(6); + assertThat(depth(deepestParagraph.getLastChild())).isEqualTo(6); + + document.accept(depthFiveVisitor); + document.accept(unlimitedVisitor); + + assertThat(depthFiveVisitor.visited).containsExactly( + "document", + "bulletList", + "listItem", + "paragraph", + "text:level1", + "blockQuote", + "paragraph", + "text:level2", + "blockQuote", + "paragraph"); + assertThat(unlimitedVisitor.visited).containsExactly( + "document", + "bulletList", + "listItem", + "paragraph", + "text:level1", + "blockQuote", + "paragraph", + "text:level2", + "blockQuote", + "paragraph", + "text:level3", + "text:> level4"); + } + private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); @@ -244,4 +293,60 @@ private String alternatingNestedList(int levels) { } return String.join("\n", lines); } + + private int depth(Node node) { + int depth = 0; + while (node.getParent() != null) { + node = node.getParent(); + depth++; + } + return depth; + } + + private static final class RecordingVisitor extends AbstractVisitor { + private final List visited = new ArrayList<>(); + + private RecordingVisitor() { + } + + private RecordingVisitor(int maxDepth) { + super(maxDepth); + } + + @Override + public void visit(Document document) { + visited.add("document"); + super.visit(document); + } + + @Override + public void visit(BulletList bulletList) { + visited.add("bulletList"); + super.visit(bulletList); + } + + @Override + public void visit(ListItem listItem) { + visited.add("listItem"); + super.visit(listItem); + } + + @Override + public void visit(BlockQuote blockQuote) { + visited.add("blockQuote"); + super.visit(blockQuote); + } + + @Override + public void visit(Paragraph paragraph) { + visited.add("paragraph"); + super.visit(paragraph); + } + + @Override + public void visit(Text text) { + visited.add("text:" + text.getLiteral()); + super.visit(text); + } + } } From 2378c7cbe03e2801063e84eea37d6b873b154417 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:00:29 +1100 Subject: [PATCH 6/9] Apply suggestion from @robinst Co-authored-by: Robin Stocker --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index dc4decf28..8faac789b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -204,7 +204,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { } /** - * Limit how many non-document block parsers may be open at once while parsing. + * Limit how many block parsers may be open at once while parsing. *

* Once the limit is reached, additional block starts are treated as plain text instead of * creating deeper nested block structure. From bef429e670479a13af66b2ef74bb39094dd53014 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:01:27 +1100 Subject: [PATCH 7/9] review --- .../src/test/java/org/commonmark/test/ParserTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 5550df225..89df86407 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -2,9 +2,8 @@ import org.commonmark.node.*; import org.commonmark.parser.*; -import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.testutil.TestResources; import org.junit.jupiter.api.Test; @@ -16,8 +15,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -279,7 +276,7 @@ private Paragraph deepestStructuredParagraph(Node document, int levels) { } private String renderText(Node node) { - return TextContentRenderer.builder().build().render(node).trim(); + return MarkdownRenderer.builder().build().render(node).trim(); } private String alternatingNestedList(int levels) { From b9dd2d5747aa8d04070811b098e2032cf9556ecd Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:08:56 +1100 Subject: [PATCH 8/9] remove stale test --- .../java/org/commonmark/test/ParserTest.java | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 89df86407..2427347ea 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -203,55 +203,6 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { assertThat(deepestParagraph.getNext()).isNull(); } - @Test - public void maxOpenBlockParsersAlignsWithVisitorDepthAtDocumentRoot() { - var parser = Parser.builder().maxOpenBlockParsers(5).build(); - - var document = parser.parse(String.join("\n", - "- level1", - " > level2", - " > > level3", - " > > > level4")); - var listBlock = document.getFirstChild(); - var listItem = listBlock.getFirstChild(); - var blockQuote1 = listItem.getLastChild(); - var blockQuote2 = blockQuote1.getLastChild(); - var deepestParagraph = blockQuote2.getLastChild(); - var depthFiveVisitor = new RecordingVisitor(5); - var unlimitedVisitor = new RecordingVisitor(); - - assertThat(depth(deepestParagraph.getFirstChild())).isEqualTo(6); - assertThat(depth(deepestParagraph.getLastChild())).isEqualTo(6); - - document.accept(depthFiveVisitor); - document.accept(unlimitedVisitor); - - assertThat(depthFiveVisitor.visited).containsExactly( - "document", - "bulletList", - "listItem", - "paragraph", - "text:level1", - "blockQuote", - "paragraph", - "text:level2", - "blockQuote", - "paragraph"); - assertThat(unlimitedVisitor.visited).containsExactly( - "document", - "bulletList", - "listItem", - "paragraph", - "text:level1", - "blockQuote", - "paragraph", - "text:level2", - "blockQuote", - "paragraph", - "text:level3", - "text:> level4"); - } - private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); From 0308e3494a8945e370b6b0833ac72346d923ff32 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:13:55 +1100 Subject: [PATCH 9/9] compile --- .../java/org/commonmark/test/ParserTest.java | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 2427347ea..337196c56 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -173,7 +173,7 @@ public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() { var document = parser.parse(alternatingNestedList(9)); var deepestParagraph = deepestStructuredParagraph(document, 8); - assertThat(renderText(deepestParagraph)).isEqualTo("level8\n- level9"); + assertThat(renderText(deepestParagraph)).isEqualTo("level8\n\\- level9"); assertThat(deepestParagraph.getNext()).isNull(); } @@ -199,7 +199,7 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { var deepestParagraph = blockQuote2.getLastChild(); assertThat(deepestParagraph).isInstanceOf(Paragraph.class); - assertThat(renderText(deepestParagraph)).isEqualTo("level3\n> level4"); + assertThat(renderText(deepestParagraph)).isEqualTo("level3\n\\> level4"); assertThat(deepestParagraph.getNext()).isNull(); } @@ -250,51 +250,4 @@ private int depth(Node node) { } return depth; } - - private static final class RecordingVisitor extends AbstractVisitor { - private final List visited = new ArrayList<>(); - - private RecordingVisitor() { - } - - private RecordingVisitor(int maxDepth) { - super(maxDepth); - } - - @Override - public void visit(Document document) { - visited.add("document"); - super.visit(document); - } - - @Override - public void visit(BulletList bulletList) { - visited.add("bulletList"); - super.visit(bulletList); - } - - @Override - public void visit(ListItem listItem) { - visited.add("listItem"); - super.visit(listItem); - } - - @Override - public void visit(BlockQuote blockQuote) { - visited.add("blockQuote"); - super.visit(blockQuote); - } - - @Override - public void visit(Paragraph paragraph) { - visited.add("paragraph"); - super.visit(paragraph); - } - - @Override - public void visit(Text text) { - visited.add("text:" + text.getLiteral()); - super.visit(text); - } - } }