[clang-tools-extra] [clang-doc] Add Markdown AST node type definitions (PR #205609)
Neil Nair via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 19:59:16 PDT 2026
https://github.com/Neil-N4 updated https://github.com/llvm/llvm-project/pull/205609
>From 4b1a963806a9654d28c92499024158df4d071859 Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Wed, 24 Jun 2026 13:41:04 -0400
Subject: [PATCH 1/7] [clang-doc] Add Markdown AST node type definitions
---
.../clang-doc/support/Markdown.h | 182 ++++++++++++++++++
.../unittests/clang-doc/CMakeLists.txt | 2 +
.../clang-doc/MarkdownParserTest.cpp | 73 +++++++
3 files changed, 257 insertions(+)
create mode 100644 clang-tools-extra/clang-doc/support/Markdown.h
create mode 100644 clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
new file mode 100644
index 0000000000000..97cadf00c9c2e
--- /dev/null
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -0,0 +1,182 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Allocator.h"
+#include <type_traits>
+
+namespace clang::doc::markdown {
+
+enum class NodeKind {
+ NK_Text,
+ NK_InlineCode,
+ NK_Emphasis,
+ NK_Strong,
+ NK_Paragraph,
+ NK_Heading,
+ NK_FencedCode,
+ NK_Table,
+ NK_UnorderedList,
+ NK_OrderedList,
+ NK_ListItem,
+ NK_BlockQuote,
+ NK_ThematicBreak,
+};
+
+struct MDNode {
+ NodeKind Kind;
+ explicit MDNode(NodeKind K) : Kind(K) {}
+};
+
+struct TextNode : MDNode {
+ llvm::StringRef Text;
+ explicit TextNode(llvm::StringRef T) : MDNode(NodeKind::NK_Text), Text(T) {}
+ static bool classof(const MDNode *N) { return N->Kind == NodeKind::NK_Text; }
+};
+static_assert(std::is_trivially_destructible_v<TextNode>);
+
+struct InlineCodeNode : MDNode {
+ llvm::StringRef Code;
+ explicit InlineCodeNode(llvm::StringRef C)
+ : MDNode(NodeKind::NK_InlineCode), Code(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_InlineCode;
+ }
+};
+static_assert(std::is_trivially_destructible_v<InlineCodeNode>);
+
+struct EmphasisNode : MDNode {
+ llvm::ArrayRef<MDNode *> Children;
+ explicit EmphasisNode(llvm::ArrayRef<MDNode *> C)
+ : MDNode(NodeKind::NK_Emphasis), Children(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_Emphasis;
+ }
+};
+static_assert(std::is_trivially_destructible_v<EmphasisNode>);
+
+struct StrongNode : MDNode {
+ llvm::ArrayRef<MDNode *> Children;
+ explicit StrongNode(llvm::ArrayRef<MDNode *> C)
+ : MDNode(NodeKind::NK_Strong), Children(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_Strong;
+ }
+};
+static_assert(std::is_trivially_destructible_v<StrongNode>);
+
+struct ParagraphNode : MDNode {
+ llvm::ArrayRef<MDNode *> Children;
+ explicit ParagraphNode(llvm::ArrayRef<MDNode *> C)
+ : MDNode(NodeKind::NK_Paragraph), Children(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_Paragraph;
+ }
+};
+static_assert(std::is_trivially_destructible_v<ParagraphNode>);
+
+struct HeadingNode : MDNode {
+ unsigned Level;
+ llvm::ArrayRef<MDNode *> Children;
+ HeadingNode(unsigned L, llvm::ArrayRef<MDNode *> C)
+ : MDNode(NodeKind::NK_Heading), Level(L), Children(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_Heading;
+ }
+};
+static_assert(std::is_trivially_destructible_v<HeadingNode>);
+
+struct FencedCodeNode : MDNode {
+ llvm::StringRef Lang;
+ llvm::ArrayRef<llvm::StringRef> Lines;
+ FencedCodeNode(llvm::StringRef L, llvm::ArrayRef<llvm::StringRef> Ls)
+ : MDNode(NodeKind::NK_FencedCode), Lang(L), Lines(Ls) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_FencedCode;
+ }
+};
+static_assert(std::is_trivially_destructible_v<FencedCodeNode>);
+
+struct TableCell {
+ llvm::ArrayRef<MDNode *> Children;
+};
+static_assert(std::is_trivially_destructible_v<TableCell>);
+
+struct TableRow {
+ llvm::ArrayRef<TableCell> Cells;
+};
+static_assert(std::is_trivially_destructible_v<TableRow>);
+
+struct TableNode : MDNode {
+ TableRow Header;
+ llvm::ArrayRef<TableRow> Body;
+ TableNode(TableRow H, llvm::ArrayRef<TableRow> B)
+ : MDNode(NodeKind::NK_Table), Header(H), Body(B) {}
+ static bool classof(const MDNode *N) { return N->Kind == NodeKind::NK_Table; }
+};
+static_assert(std::is_trivially_destructible_v<TableNode>);
+
+struct ListItemNode : MDNode {
+ llvm::ArrayRef<MDNode *> Children;
+ explicit ListItemNode(llvm::ArrayRef<MDNode *> C)
+ : MDNode(NodeKind::NK_ListItem), Children(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_ListItem;
+ }
+};
+static_assert(std::is_trivially_destructible_v<ListItemNode>);
+
+struct UnorderedListNode : MDNode {
+ llvm::ArrayRef<ListItemNode *> Items;
+ explicit UnorderedListNode(llvm::ArrayRef<ListItemNode *> I)
+ : MDNode(NodeKind::NK_UnorderedList), Items(I) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_UnorderedList;
+ }
+};
+static_assert(std::is_trivially_destructible_v<UnorderedListNode>);
+
+struct OrderedListNode : MDNode {
+ unsigned Start;
+ llvm::ArrayRef<ListItemNode *> Items;
+ OrderedListNode(unsigned S, llvm::ArrayRef<ListItemNode *> I)
+ : MDNode(NodeKind::NK_OrderedList), Start(S), Items(I) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_OrderedList;
+ }
+};
+static_assert(std::is_trivially_destructible_v<OrderedListNode>);
+
+struct BlockQuoteNode : MDNode {
+ llvm::ArrayRef<MDNode *> Children;
+ explicit BlockQuoteNode(llvm::ArrayRef<MDNode *> C)
+ : MDNode(NodeKind::NK_BlockQuote), Children(C) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_BlockQuote;
+ }
+};
+static_assert(std::is_trivially_destructible_v<BlockQuoteNode>);
+
+struct ThematicBreakNode : MDNode {
+ ThematicBreakNode() : MDNode(NodeKind::NK_ThematicBreak) {}
+ static bool classof(const MDNode *N) {
+ return N->Kind == NodeKind::NK_ThematicBreak;
+ }
+};
+static_assert(std::is_trivially_destructible_v<ThematicBreakNode>);
+
+llvm::ArrayRef<MDNode *> parseMarkdown(llvm::StringRef Text,
+ llvm::BumpPtrAllocator &Arena);
+
+} // namespace clang::doc::markdown
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
\ No newline at end of file
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 01b34ec9a791e..935df6da8ac78 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -31,6 +31,7 @@ add_extra_unittest(ClangDocTests
SerializeTest.cpp
YAMLGeneratorTest.cpp
JSONGeneratorTest.cpp
+ MarkdownParserTest.cpp
)
clang_target_link_libraries(ClangDocTests
@@ -49,5 +50,6 @@ clang_target_link_libraries(ClangDocTests
target_link_libraries(ClangDocTests
PRIVATE
clangDoc
+ clangDocSupport
LLVMTestingSupport
)
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
new file mode 100644
index 0000000000000..c0b554ad67f7d
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -0,0 +1,73 @@
+#include "support/Markdown.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Casting.h"
+#include "gtest/gtest.h"
+
+using namespace clang::doc::markdown;
+using namespace llvm;
+
+namespace {
+
+TEST(MarkdownNodeTest, TextNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) TextNode("hello");
+ EXPECT_EQ(N->Kind, NodeKind::NK_Text);
+ EXPECT_EQ(N->Text, "hello");
+ EXPECT_TRUE(isa<TextNode>(N));
+}
+
+TEST(MarkdownNodeTest, FencedCodeNode) {
+ BumpPtrAllocator Arena;
+ StringRef Lines[] = {"int x = 0;"};
+ auto *N = new (Arena) FencedCodeNode("cpp", ArrayRef(Lines));
+ EXPECT_EQ(N->Kind, NodeKind::NK_FencedCode);
+ EXPECT_EQ(N->Lang, "cpp");
+ EXPECT_EQ(N->Lines.size(), 1u);
+ EXPECT_TRUE(isa<FencedCodeNode>(N));
+}
+
+TEST(MarkdownNodeTest, HeadingNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) HeadingNode(2, {});
+ EXPECT_EQ(N->Kind, NodeKind::NK_Heading);
+ EXPECT_EQ(N->Level, 2u);
+ EXPECT_TRUE(isa<HeadingNode>(N));
+}
+
+TEST(MarkdownNodeTest, ThematicBreakNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) ThematicBreakNode();
+ EXPECT_EQ(N->Kind, NodeKind::NK_ThematicBreak);
+ EXPECT_TRUE(isa<ThematicBreakNode>(N));
+}
+
+TEST(MarkdownNodeTest, InlineCodeNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) InlineCodeNode("foo()");
+ EXPECT_EQ(N->Kind, NodeKind::NK_InlineCode);
+ EXPECT_EQ(N->Code, "foo()");
+ EXPECT_TRUE(isa<InlineCodeNode>(N));
+}
+
+TEST(MarkdownNodeTest, EmphasisNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) EmphasisNode({});
+ EXPECT_EQ(N->Kind, NodeKind::NK_Emphasis);
+ EXPECT_TRUE(isa<EmphasisNode>(N));
+}
+
+TEST(MarkdownNodeTest, UnorderedListNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) UnorderedListNode({});
+ EXPECT_EQ(N->Kind, NodeKind::NK_UnorderedList);
+ EXPECT_TRUE(isa<UnorderedListNode>(N));
+}
+
+TEST(MarkdownNodeTest, ParagraphNode) {
+ BumpPtrAllocator Arena;
+ auto *N = new (Arena) ParagraphNode({});
+ EXPECT_EQ(N->Kind, NodeKind::NK_Paragraph);
+ EXPECT_TRUE(isa<ParagraphNode>(N));
+}
+
+} // namespace
\ No newline at end of file
>From cd0760862b396c7bf8657c631980b6d702eb1a32 Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Wed, 24 Jun 2026 21:19:23 -0400
Subject: [PATCH 2/7] [clang-doc] Address review feedback: rename Node, fix
tests, EOF newlines
---
.../clang-doc/support/Markdown.h | 116 ++++++++----------
.../clang-doc/MarkdownParserTest.cpp | 59 ++++-----
2 files changed, 75 insertions(+), 100 deletions(-)
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
index 97cadf00c9c2e..d05689f32e608 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.h
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -11,7 +11,6 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Allocator.h"
#include <type_traits>
namespace clang::doc::markdown {
@@ -32,82 +31,78 @@ enum class NodeKind {
NK_ThematicBreak,
};
-struct MDNode {
+struct Node {
NodeKind Kind;
- explicit MDNode(NodeKind K) : Kind(K) {}
+ explicit Node(NodeKind K) : Kind(K) {}
};
-struct TextNode : MDNode {
+struct TextNode : Node {
llvm::StringRef Text;
- explicit TextNode(llvm::StringRef T) : MDNode(NodeKind::NK_Text), Text(T) {}
- static bool classof(const MDNode *N) { return N->Kind == NodeKind::NK_Text; }
+ explicit TextNode(llvm::StringRef T) : Node(NodeKind::NK_Text), Text(T) {}
+ static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Text; }
};
static_assert(std::is_trivially_destructible_v<TextNode>);
-struct InlineCodeNode : MDNode {
+struct InlineCodeNode : Node {
llvm::StringRef Code;
explicit InlineCodeNode(llvm::StringRef C)
- : MDNode(NodeKind::NK_InlineCode), Code(C) {}
- static bool classof(const MDNode *N) {
+ : Node(NodeKind::NK_InlineCode), Code(C) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_InlineCode;
}
};
static_assert(std::is_trivially_destructible_v<InlineCodeNode>);
-struct EmphasisNode : MDNode {
- llvm::ArrayRef<MDNode *> Children;
- explicit EmphasisNode(llvm::ArrayRef<MDNode *> C)
- : MDNode(NodeKind::NK_Emphasis), Children(C) {}
- static bool classof(const MDNode *N) {
+struct EmphasisNode : Node {
+ llvm::ArrayRef<Node *> Children;
+ explicit EmphasisNode(llvm::ArrayRef<Node *> C)
+ : Node(NodeKind::NK_Emphasis), Children(C) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_Emphasis;
}
};
static_assert(std::is_trivially_destructible_v<EmphasisNode>);
-struct StrongNode : MDNode {
- llvm::ArrayRef<MDNode *> Children;
- explicit StrongNode(llvm::ArrayRef<MDNode *> C)
- : MDNode(NodeKind::NK_Strong), Children(C) {}
- static bool classof(const MDNode *N) {
- return N->Kind == NodeKind::NK_Strong;
- }
+struct StrongNode : Node {
+ llvm::ArrayRef<Node *> Children;
+ explicit StrongNode(llvm::ArrayRef<Node *> C)
+ : Node(NodeKind::NK_Strong), Children(C) {}
+ static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Strong; }
};
static_assert(std::is_trivially_destructible_v<StrongNode>);
-struct ParagraphNode : MDNode {
- llvm::ArrayRef<MDNode *> Children;
- explicit ParagraphNode(llvm::ArrayRef<MDNode *> C)
- : MDNode(NodeKind::NK_Paragraph), Children(C) {}
- static bool classof(const MDNode *N) {
+struct ParagraphNode : Node {
+ llvm::ArrayRef<Node *> Children;
+ explicit ParagraphNode(llvm::ArrayRef<Node *> C)
+ : Node(NodeKind::NK_Paragraph), Children(C) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_Paragraph;
}
};
static_assert(std::is_trivially_destructible_v<ParagraphNode>);
-struct HeadingNode : MDNode {
+struct HeadingNode : Node {
unsigned Level;
- llvm::ArrayRef<MDNode *> Children;
- HeadingNode(unsigned L, llvm::ArrayRef<MDNode *> C)
- : MDNode(NodeKind::NK_Heading), Level(L), Children(C) {}
- static bool classof(const MDNode *N) {
- return N->Kind == NodeKind::NK_Heading;
- }
+ llvm::ArrayRef<Node *> Children;
+ HeadingNode(unsigned L, llvm::ArrayRef<Node *> C)
+ : Node(NodeKind::NK_Heading), Level(L), Children(C) {}
+ static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Heading; }
};
static_assert(std::is_trivially_destructible_v<HeadingNode>);
-struct FencedCodeNode : MDNode {
+struct FencedCodeNode : Node {
llvm::StringRef Lang;
llvm::ArrayRef<llvm::StringRef> Lines;
FencedCodeNode(llvm::StringRef L, llvm::ArrayRef<llvm::StringRef> Ls)
- : MDNode(NodeKind::NK_FencedCode), Lang(L), Lines(Ls) {}
- static bool classof(const MDNode *N) {
+ : Node(NodeKind::NK_FencedCode), Lang(L), Lines(Ls) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_FencedCode;
}
};
static_assert(std::is_trivially_destructible_v<FencedCodeNode>);
struct TableCell {
- llvm::ArrayRef<MDNode *> Children;
+ llvm::ArrayRef<Node *> Children;
};
static_assert(std::is_trivially_destructible_v<TableCell>);
@@ -116,67 +111,64 @@ struct TableRow {
};
static_assert(std::is_trivially_destructible_v<TableRow>);
-struct TableNode : MDNode {
+struct TableNode : Node {
TableRow Header;
llvm::ArrayRef<TableRow> Body;
TableNode(TableRow H, llvm::ArrayRef<TableRow> B)
- : MDNode(NodeKind::NK_Table), Header(H), Body(B) {}
- static bool classof(const MDNode *N) { return N->Kind == NodeKind::NK_Table; }
+ : Node(NodeKind::NK_Table), Header(H), Body(B) {}
+ static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Table; }
};
static_assert(std::is_trivially_destructible_v<TableNode>);
-struct ListItemNode : MDNode {
- llvm::ArrayRef<MDNode *> Children;
- explicit ListItemNode(llvm::ArrayRef<MDNode *> C)
- : MDNode(NodeKind::NK_ListItem), Children(C) {}
- static bool classof(const MDNode *N) {
+struct ListItemNode : Node {
+ llvm::ArrayRef<Node *> Children;
+ explicit ListItemNode(llvm::ArrayRef<Node *> C)
+ : Node(NodeKind::NK_ListItem), Children(C) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_ListItem;
}
};
static_assert(std::is_trivially_destructible_v<ListItemNode>);
-struct UnorderedListNode : MDNode {
+struct UnorderedListNode : Node {
llvm::ArrayRef<ListItemNode *> Items;
explicit UnorderedListNode(llvm::ArrayRef<ListItemNode *> I)
- : MDNode(NodeKind::NK_UnorderedList), Items(I) {}
- static bool classof(const MDNode *N) {
+ : Node(NodeKind::NK_UnorderedList), Items(I) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_UnorderedList;
}
};
static_assert(std::is_trivially_destructible_v<UnorderedListNode>);
-struct OrderedListNode : MDNode {
+struct OrderedListNode : Node {
unsigned Start;
llvm::ArrayRef<ListItemNode *> Items;
OrderedListNode(unsigned S, llvm::ArrayRef<ListItemNode *> I)
- : MDNode(NodeKind::NK_OrderedList), Start(S), Items(I) {}
- static bool classof(const MDNode *N) {
+ : Node(NodeKind::NK_OrderedList), Start(S), Items(I) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_OrderedList;
}
};
static_assert(std::is_trivially_destructible_v<OrderedListNode>);
-struct BlockQuoteNode : MDNode {
- llvm::ArrayRef<MDNode *> Children;
- explicit BlockQuoteNode(llvm::ArrayRef<MDNode *> C)
- : MDNode(NodeKind::NK_BlockQuote), Children(C) {}
- static bool classof(const MDNode *N) {
+struct BlockQuoteNode : Node {
+ llvm::ArrayRef<Node *> Children;
+ explicit BlockQuoteNode(llvm::ArrayRef<Node *> C)
+ : Node(NodeKind::NK_BlockQuote), Children(C) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_BlockQuote;
}
};
static_assert(std::is_trivially_destructible_v<BlockQuoteNode>);
-struct ThematicBreakNode : MDNode {
- ThematicBreakNode() : MDNode(NodeKind::NK_ThematicBreak) {}
- static bool classof(const MDNode *N) {
+struct ThematicBreakNode : Node {
+ ThematicBreakNode() : Node(NodeKind::NK_ThematicBreak) {}
+ static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_ThematicBreak;
}
};
static_assert(std::is_trivially_destructible_v<ThematicBreakNode>);
-llvm::ArrayRef<MDNode *> parseMarkdown(llvm::StringRef Text,
- llvm::BumpPtrAllocator &Arena);
-
} // namespace clang::doc::markdown
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
\ No newline at end of file
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
index c0b554ad67f7d..e65c07debb5e8 100644
--- a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -1,5 +1,4 @@
#include "support/Markdown.h"
-#include "llvm/Support/Allocator.h"
#include "llvm/Support/Casting.h"
#include "gtest/gtest.h"
@@ -9,65 +8,49 @@ using namespace llvm;
namespace {
TEST(MarkdownNodeTest, TextNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) TextNode("hello");
- EXPECT_EQ(N->Kind, NodeKind::NK_Text);
- EXPECT_EQ(N->Text, "hello");
- EXPECT_TRUE(isa<TextNode>(N));
+ TextNode N("hello");
+ EXPECT_EQ(N.Kind, NodeKind::NK_Text);
+ EXPECT_EQ(N.Text, "hello");
}
TEST(MarkdownNodeTest, FencedCodeNode) {
- BumpPtrAllocator Arena;
StringRef Lines[] = {"int x = 0;"};
- auto *N = new (Arena) FencedCodeNode("cpp", ArrayRef(Lines));
- EXPECT_EQ(N->Kind, NodeKind::NK_FencedCode);
- EXPECT_EQ(N->Lang, "cpp");
- EXPECT_EQ(N->Lines.size(), 1u);
- EXPECT_TRUE(isa<FencedCodeNode>(N));
+ FencedCodeNode N("cpp", ArrayRef(Lines));
+ EXPECT_EQ(N.Kind, NodeKind::NK_FencedCode);
+ EXPECT_EQ(N.Lang, "cpp");
+ EXPECT_EQ(N.Lines.size(), 1u);
}
TEST(MarkdownNodeTest, HeadingNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) HeadingNode(2, {});
- EXPECT_EQ(N->Kind, NodeKind::NK_Heading);
- EXPECT_EQ(N->Level, 2u);
- EXPECT_TRUE(isa<HeadingNode>(N));
+ HeadingNode N(2, {});
+ EXPECT_EQ(N.Kind, NodeKind::NK_Heading);
+ EXPECT_EQ(N.Level, 2u);
}
TEST(MarkdownNodeTest, ThematicBreakNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) ThematicBreakNode();
- EXPECT_EQ(N->Kind, NodeKind::NK_ThematicBreak);
- EXPECT_TRUE(isa<ThematicBreakNode>(N));
+ ThematicBreakNode N;
+ EXPECT_EQ(N.Kind, NodeKind::NK_ThematicBreak);
}
TEST(MarkdownNodeTest, InlineCodeNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) InlineCodeNode("foo()");
- EXPECT_EQ(N->Kind, NodeKind::NK_InlineCode);
- EXPECT_EQ(N->Code, "foo()");
- EXPECT_TRUE(isa<InlineCodeNode>(N));
+ InlineCodeNode N("foo()");
+ EXPECT_EQ(N.Kind, NodeKind::NK_InlineCode);
+ EXPECT_EQ(N.Code, "foo()");
}
TEST(MarkdownNodeTest, EmphasisNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) EmphasisNode({});
- EXPECT_EQ(N->Kind, NodeKind::NK_Emphasis);
- EXPECT_TRUE(isa<EmphasisNode>(N));
+ EmphasisNode N({});
+ EXPECT_EQ(N.Kind, NodeKind::NK_Emphasis);
}
TEST(MarkdownNodeTest, UnorderedListNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) UnorderedListNode({});
- EXPECT_EQ(N->Kind, NodeKind::NK_UnorderedList);
- EXPECT_TRUE(isa<UnorderedListNode>(N));
+ UnorderedListNode N({});
+ EXPECT_EQ(N.Kind, NodeKind::NK_UnorderedList);
}
TEST(MarkdownNodeTest, ParagraphNode) {
- BumpPtrAllocator Arena;
- auto *N = new (Arena) ParagraphNode({});
- EXPECT_EQ(N->Kind, NodeKind::NK_Paragraph);
- EXPECT_TRUE(isa<ParagraphNode>(N));
+ ParagraphNode N({});
+ EXPECT_EQ(N.Kind, NodeKind::NK_Paragraph);
}
} // namespace
\ No newline at end of file
>From bb5c2056f4860546a4d4991c5672574ea3c6a0e6 Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Fri, 26 Jun 2026 15:33:55 -0400
Subject: [PATCH 3/7] [clang-doc] Address review feedback: proper classes with
getters and dump, fix tests
---
.../clang-doc/support/Markdown.h | 114 +++++++++++++++---
.../unittests/clang-doc/CMakeLists.txt | 1 -
.../clang-doc/MarkdownParserTest.cpp | 27 +++--
3 files changed, 118 insertions(+), 24 deletions(-)
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
index d05689f32e608..b5c6ccbfe0255 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.h
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -11,6 +11,8 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/raw_ostream.h"
#include <type_traits>
namespace clang::doc::markdown {
@@ -31,70 +33,115 @@ enum class NodeKind {
NK_ThematicBreak,
};
-struct Node {
+class Node {
+public:
NodeKind Kind;
explicit Node(NodeKind K) : Kind(K) {}
+ void dump() const { llvm::errs() << "Node\n"; }
+ static bool classof(const Node *) { return true; }
};
-struct TextNode : Node {
+class TextNode : public Node {
llvm::StringRef Text;
+
+public:
explicit TextNode(llvm::StringRef T) : Node(NodeKind::NK_Text), Text(T) {}
+ llvm::StringRef getText() const { return Text; }
+ void dump() const { llvm::errs() << "TextNode: " << Text << "\n"; }
static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Text; }
};
static_assert(std::is_trivially_destructible_v<TextNode>);
-struct InlineCodeNode : Node {
+class InlineCodeNode : public Node {
llvm::StringRef Code;
+
+public:
explicit InlineCodeNode(llvm::StringRef C)
: Node(NodeKind::NK_InlineCode), Code(C) {}
+ llvm::StringRef getCode() const { return Code; }
+ void dump() const { llvm::errs() << "InlineCodeNode: " << Code << "\n"; }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_InlineCode;
}
};
static_assert(std::is_trivially_destructible_v<InlineCodeNode>);
-struct EmphasisNode : Node {
+class EmphasisNode : public Node {
llvm::ArrayRef<Node *> Children;
+
+public:
explicit EmphasisNode(llvm::ArrayRef<Node *> C)
: Node(NodeKind::NK_Emphasis), Children(C) {}
+ llvm::ArrayRef<Node *> getChildren() const { return Children; }
+ void dump() const {
+ llvm::errs() << "EmphasisNode (" << Children.size() << " children)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_Emphasis;
}
};
static_assert(std::is_trivially_destructible_v<EmphasisNode>);
-struct StrongNode : Node {
+class StrongNode : public Node {
llvm::ArrayRef<Node *> Children;
+
+public:
explicit StrongNode(llvm::ArrayRef<Node *> C)
: Node(NodeKind::NK_Strong), Children(C) {}
+ llvm::ArrayRef<Node *> getChildren() const { return Children; }
+ void dump() const {
+ llvm::errs() << "StrongNode (" << Children.size() << " children)\n";
+ }
static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Strong; }
};
static_assert(std::is_trivially_destructible_v<StrongNode>);
-struct ParagraphNode : Node {
+class ParagraphNode : public Node {
llvm::ArrayRef<Node *> Children;
+
+public:
explicit ParagraphNode(llvm::ArrayRef<Node *> C)
: Node(NodeKind::NK_Paragraph), Children(C) {}
+ llvm::ArrayRef<Node *> getChildren() const { return Children; }
+ void dump() const {
+ llvm::errs() << "ParagraphNode (" << Children.size() << " children)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_Paragraph;
}
};
static_assert(std::is_trivially_destructible_v<ParagraphNode>);
-struct HeadingNode : Node {
+class HeadingNode : public Node {
unsigned Level;
llvm::ArrayRef<Node *> Children;
+
+public:
HeadingNode(unsigned L, llvm::ArrayRef<Node *> C)
: Node(NodeKind::NK_Heading), Level(L), Children(C) {}
+ unsigned getLevel() const { return Level; }
+ llvm::ArrayRef<Node *> getChildren() const { return Children; }
+ void dump() const {
+ llvm::errs() << "HeadingNode: level=" << Level << " (" << Children.size()
+ << " children)\n";
+ }
static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Heading; }
};
static_assert(std::is_trivially_destructible_v<HeadingNode>);
-struct FencedCodeNode : Node {
+class FencedCodeNode : public Node {
llvm::StringRef Lang;
llvm::ArrayRef<llvm::StringRef> Lines;
+
+public:
FencedCodeNode(llvm::StringRef L, llvm::ArrayRef<llvm::StringRef> Ls)
: Node(NodeKind::NK_FencedCode), Lang(L), Lines(Ls) {}
+ llvm::StringRef getLang() const { return Lang; }
+ llvm::ArrayRef<llvm::StringRef> getLines() const { return Lines; }
+ void dump() const {
+ llvm::errs() << "FencedCodeNode: lang=" << Lang << " (" << Lines.size()
+ << " lines)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_FencedCode;
}
@@ -111,58 +158,95 @@ struct TableRow {
};
static_assert(std::is_trivially_destructible_v<TableRow>);
-struct TableNode : Node {
+class TableNode : public Node {
TableRow Header;
llvm::ArrayRef<TableRow> Body;
+
+public:
TableNode(TableRow H, llvm::ArrayRef<TableRow> B)
: Node(NodeKind::NK_Table), Header(H), Body(B) {}
+ const TableRow &getHeader() const { return Header; }
+ llvm::ArrayRef<TableRow> getBody() const { return Body; }
+ void dump() const {
+ llvm::errs() << "TableNode: " << Header.Cells.size() << " header cells, "
+ << Body.size() << " rows\n";
+ }
static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Table; }
};
static_assert(std::is_trivially_destructible_v<TableNode>);
-struct ListItemNode : Node {
+class ListItemNode : public Node {
llvm::ArrayRef<Node *> Children;
+
+public:
explicit ListItemNode(llvm::ArrayRef<Node *> C)
: Node(NodeKind::NK_ListItem), Children(C) {}
+ llvm::ArrayRef<Node *> getChildren() const { return Children; }
+ void dump() const {
+ llvm::errs() << "ListItemNode (" << Children.size() << " children)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_ListItem;
}
};
static_assert(std::is_trivially_destructible_v<ListItemNode>);
-struct UnorderedListNode : Node {
+class UnorderedListNode : public Node {
llvm::ArrayRef<ListItemNode *> Items;
+
+public:
+ UnorderedListNode() : Node(NodeKind::NK_UnorderedList), Items({}) {}
explicit UnorderedListNode(llvm::ArrayRef<ListItemNode *> I)
: Node(NodeKind::NK_UnorderedList), Items(I) {}
+ llvm::ArrayRef<ListItemNode *> getItems() const { return Items; }
+ void dump() const {
+ llvm::errs() << "UnorderedListNode (" << Items.size() << " items)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_UnorderedList;
}
};
static_assert(std::is_trivially_destructible_v<UnorderedListNode>);
-struct OrderedListNode : Node {
+class OrderedListNode : public Node {
unsigned Start;
llvm::ArrayRef<ListItemNode *> Items;
+
+public:
OrderedListNode(unsigned S, llvm::ArrayRef<ListItemNode *> I)
: Node(NodeKind::NK_OrderedList), Start(S), Items(I) {}
+ unsigned getStart() const { return Start; }
+ llvm::ArrayRef<ListItemNode *> getItems() const { return Items; }
+ void dump() const {
+ llvm::errs() << "OrderedListNode: start=" << Start << " (" << Items.size()
+ << " items)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_OrderedList;
}
};
static_assert(std::is_trivially_destructible_v<OrderedListNode>);
-struct BlockQuoteNode : Node {
+class BlockQuoteNode : public Node {
llvm::ArrayRef<Node *> Children;
+
+public:
explicit BlockQuoteNode(llvm::ArrayRef<Node *> C)
: Node(NodeKind::NK_BlockQuote), Children(C) {}
+ llvm::ArrayRef<Node *> getChildren() const { return Children; }
+ void dump() const {
+ llvm::errs() << "BlockQuoteNode (" << Children.size() << " children)\n";
+ }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_BlockQuote;
}
};
static_assert(std::is_trivially_destructible_v<BlockQuoteNode>);
-struct ThematicBreakNode : Node {
+class ThematicBreakNode : public Node {
+public:
ThematicBreakNode() : Node(NodeKind::NK_ThematicBreak) {}
+ void dump() const { llvm::errs() << "ThematicBreakNode\n"; }
static bool classof(const Node *N) {
return N->Kind == NodeKind::NK_ThematicBreak;
}
@@ -171,4 +255,4 @@ static_assert(std::is_trivially_destructible_v<ThematicBreakNode>);
} // namespace clang::doc::markdown
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
\ No newline at end of file
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 935df6da8ac78..688a547a7f031 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -50,6 +50,5 @@ clang_target_link_libraries(ClangDocTests
target_link_libraries(ClangDocTests
PRIVATE
clangDoc
- clangDocSupport
LLVMTestingSupport
)
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
index e65c07debb5e8..4b7d3e4b7bb4b 100644
--- a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -1,5 +1,12 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
#include "support/Markdown.h"
-#include "llvm/Support/Casting.h"
#include "gtest/gtest.h"
using namespace clang::doc::markdown;
@@ -10,21 +17,22 @@ namespace {
TEST(MarkdownNodeTest, TextNode) {
TextNode N("hello");
EXPECT_EQ(N.Kind, NodeKind::NK_Text);
- EXPECT_EQ(N.Text, "hello");
+ EXPECT_EQ(N.getText(), "hello");
}
TEST(MarkdownNodeTest, FencedCodeNode) {
- StringRef Lines[] = {"int x = 0;"};
+ StringRef Lines[] = {"int x = 0;", "int y = 1;", "return x + y;"};
FencedCodeNode N("cpp", ArrayRef(Lines));
EXPECT_EQ(N.Kind, NodeKind::NK_FencedCode);
- EXPECT_EQ(N.Lang, "cpp");
- EXPECT_EQ(N.Lines.size(), 1u);
+ EXPECT_EQ(N.getLang(), "cpp");
+ EXPECT_EQ(N.getLines().size(), 3u);
+ EXPECT_EQ(N.getLines()[1], "int y = 1;");
}
TEST(MarkdownNodeTest, HeadingNode) {
HeadingNode N(2, {});
EXPECT_EQ(N.Kind, NodeKind::NK_Heading);
- EXPECT_EQ(N.Level, 2u);
+ EXPECT_EQ(N.getLevel(), 2u);
}
TEST(MarkdownNodeTest, ThematicBreakNode) {
@@ -35,22 +43,25 @@ TEST(MarkdownNodeTest, ThematicBreakNode) {
TEST(MarkdownNodeTest, InlineCodeNode) {
InlineCodeNode N("foo()");
EXPECT_EQ(N.Kind, NodeKind::NK_InlineCode);
- EXPECT_EQ(N.Code, "foo()");
+ EXPECT_EQ(N.getCode(), "foo()");
}
TEST(MarkdownNodeTest, EmphasisNode) {
EmphasisNode N({});
EXPECT_EQ(N.Kind, NodeKind::NK_Emphasis);
+ EXPECT_TRUE(N.getChildren().empty());
}
TEST(MarkdownNodeTest, UnorderedListNode) {
- UnorderedListNode N({});
+ UnorderedListNode N;
EXPECT_EQ(N.Kind, NodeKind::NK_UnorderedList);
+ EXPECT_TRUE(N.getItems().empty());
}
TEST(MarkdownNodeTest, ParagraphNode) {
ParagraphNode N({});
EXPECT_EQ(N.Kind, NodeKind::NK_Paragraph);
+ EXPECT_TRUE(N.getChildren().empty());
}
} // namespace
\ No newline at end of file
>From db35d079da9f0d2122fe03f6d5ac3464a395bb91 Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Fri, 26 Jun 2026 19:12:37 -0400
Subject: [PATCH 4/7] [clang-doc] Redesign nodes using ilist_node and separate
Block/Inline hierarchies
---
.../clang-doc/support/Markdown.h | 295 +++++++++---------
.../clang-doc/MarkdownParserTest.cpp | 20 +-
2 files changed, 162 insertions(+), 153 deletions(-)
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
index b5c6ccbfe0255..1920b7a08b8fe 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.h
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -9,19 +9,23 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
-#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/simple_ilist.h"
+#include "llvm/Support/Allocator.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/StringSaver.h"
#include "llvm/Support/raw_ostream.h"
#include <type_traits>
namespace clang::doc::markdown {
enum class NodeKind {
+ // Inline nodes
NK_Text,
NK_InlineCode,
NK_Emphasis,
NK_Strong,
+ // Block nodes
NK_Paragraph,
NK_Heading,
NK_FencedCode,
@@ -31,227 +35,234 @@ enum class NodeKind {
NK_ListItem,
NK_BlockQuote,
NK_ThematicBreak,
+ NK_Document,
};
-class Node {
-public:
+// Forward declarations
+struct InlineNode;
+struct BlockNode;
+
+//===----------------------------------------------------------------------===//
+// Inline nodes
+//===----------------------------------------------------------------------===//
+
+struct InlineNode
+ : llvm::ilist_node<InlineNode, llvm::ilist_sentinel_tracking<true>> {
NodeKind Kind;
- explicit Node(NodeKind K) : Kind(K) {}
- void dump() const { llvm::errs() << "Node\n"; }
- static bool classof(const Node *) { return true; }
+ explicit InlineNode(NodeKind K) : Kind(K) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const;
};
-class TextNode : public Node {
+struct TextNode : InlineNode {
+private:
llvm::StringRef Text;
public:
- explicit TextNode(llvm::StringRef T) : Node(NodeKind::NK_Text), Text(T) {}
+ explicit TextNode(llvm::StringRef T)
+ : InlineNode(NodeKind::NK_Text), Text(T) {}
llvm::StringRef getText() const { return Text; }
- void dump() const { llvm::errs() << "TextNode: " << Text << "\n"; }
- static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Text; }
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "TextNode: " << Text << "\n";
+ }
+ static bool classof(const InlineNode *N) {
+ return N->Kind == NodeKind::NK_Text;
+ }
};
static_assert(std::is_trivially_destructible_v<TextNode>);
-class InlineCodeNode : public Node {
+struct InlineCodeNode : InlineNode {
+private:
llvm::StringRef Code;
public:
explicit InlineCodeNode(llvm::StringRef C)
- : Node(NodeKind::NK_InlineCode), Code(C) {}
+ : InlineNode(NodeKind::NK_InlineCode), Code(C) {}
llvm::StringRef getCode() const { return Code; }
- void dump() const { llvm::errs() << "InlineCodeNode: " << Code << "\n"; }
- static bool classof(const Node *N) {
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "InlineCodeNode: " << Code << "\n";
+ }
+ static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_InlineCode;
}
};
static_assert(std::is_trivially_destructible_v<InlineCodeNode>);
-class EmphasisNode : public Node {
- llvm::ArrayRef<Node *> Children;
-
-public:
- explicit EmphasisNode(llvm::ArrayRef<Node *> C)
- : Node(NodeKind::NK_Emphasis), Children(C) {}
- llvm::ArrayRef<Node *> getChildren() const { return Children; }
- void dump() const {
- llvm::errs() << "EmphasisNode (" << Children.size() << " children)\n";
+struct EmphasisNode : InlineNode {
+ llvm::simple_ilist<InlineNode, llvm::ilist_sentinel_tracking<true>> Children;
+ EmphasisNode() : InlineNode(NodeKind::NK_Emphasis) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "EmphasisNode\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Emphasis;
}
};
-static_assert(std::is_trivially_destructible_v<EmphasisNode>);
-
-class StrongNode : public Node {
- llvm::ArrayRef<Node *> Children;
-public:
- explicit StrongNode(llvm::ArrayRef<Node *> C)
- : Node(NodeKind::NK_Strong), Children(C) {}
- llvm::ArrayRef<Node *> getChildren() const { return Children; }
- void dump() const {
- llvm::errs() << "StrongNode (" << Children.size() << " children)\n";
+struct StrongNode : InlineNode {
+ llvm::simple_ilist<InlineNode, llvm::ilist_sentinel_tracking<true>> Children;
+ StrongNode() : InlineNode(NodeKind::NK_Strong) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "StrongNode\n";
+ }
+ static bool classof(const InlineNode *N) {
+ return N->Kind == NodeKind::NK_Strong;
}
- static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Strong; }
};
-static_assert(std::is_trivially_destructible_v<StrongNode>);
-class ParagraphNode : public Node {
- llvm::ArrayRef<Node *> Children;
+//===----------------------------------------------------------------------===//
+// Block nodes
+//===----------------------------------------------------------------------===//
-public:
- explicit ParagraphNode(llvm::ArrayRef<Node *> C)
- : Node(NodeKind::NK_Paragraph), Children(C) {}
- llvm::ArrayRef<Node *> getChildren() const { return Children; }
- void dump() const {
- llvm::errs() << "ParagraphNode (" << Children.size() << " children)\n";
+struct BlockNode
+ : llvm::ilist_node<BlockNode, llvm::ilist_sentinel_tracking<true>> {
+ NodeKind Kind;
+ explicit BlockNode(NodeKind K) : Kind(K) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const;
+};
+
+using InlineList =
+ llvm::simple_ilist<InlineNode, llvm::ilist_sentinel_tracking<true>>;
+using BlockList =
+ llvm::simple_ilist<BlockNode, llvm::ilist_sentinel_tracking<true>>;
+
+struct ParagraphNode : BlockNode {
+ InlineList Children;
+ ParagraphNode() : BlockNode(NodeKind::NK_Paragraph) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "ParagraphNode\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Paragraph;
}
};
-static_assert(std::is_trivially_destructible_v<ParagraphNode>);
-class HeadingNode : public Node {
+struct HeadingNode : BlockNode {
+private:
unsigned Level;
- llvm::ArrayRef<Node *> Children;
public:
- HeadingNode(unsigned L, llvm::ArrayRef<Node *> C)
- : Node(NodeKind::NK_Heading), Level(L), Children(C) {}
+ InlineList Children;
+ explicit HeadingNode(unsigned L)
+ : BlockNode(NodeKind::NK_Heading), Level(L) {}
unsigned getLevel() const { return Level; }
- llvm::ArrayRef<Node *> getChildren() const { return Children; }
- void dump() const {
- llvm::errs() << "HeadingNode: level=" << Level << " (" << Children.size()
- << " children)\n";
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "HeadingNode: level=" << Level << "\n";
+ }
+ static bool classof(const BlockNode *N) {
+ return N->Kind == NodeKind::NK_Heading;
}
- static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Heading; }
};
-static_assert(std::is_trivially_destructible_v<HeadingNode>);
-class FencedCodeNode : public Node {
+struct FencedCodeNode : BlockNode {
+private:
llvm::StringRef Lang;
- llvm::ArrayRef<llvm::StringRef> Lines;
+ llvm::StringRef Code;
public:
- FencedCodeNode(llvm::StringRef L, llvm::ArrayRef<llvm::StringRef> Ls)
- : Node(NodeKind::NK_FencedCode), Lang(L), Lines(Ls) {}
+ FencedCodeNode(llvm::StringRef L, llvm::StringRef C)
+ : BlockNode(NodeKind::NK_FencedCode), Lang(L), Code(C) {}
llvm::StringRef getLang() const { return Lang; }
- llvm::ArrayRef<llvm::StringRef> getLines() const { return Lines; }
- void dump() const {
- llvm::errs() << "FencedCodeNode: lang=" << Lang << " (" << Lines.size()
- << " lines)\n";
+ llvm::StringRef getCode() const { return Code; }
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "FencedCodeNode: lang=" << Lang << "\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_FencedCode;
}
};
static_assert(std::is_trivially_destructible_v<FencedCodeNode>);
-struct TableCell {
- llvm::ArrayRef<Node *> Children;
-};
-static_assert(std::is_trivially_destructible_v<TableCell>);
-
-struct TableRow {
- llvm::ArrayRef<TableCell> Cells;
-};
-static_assert(std::is_trivially_destructible_v<TableRow>);
-
-class TableNode : public Node {
- TableRow Header;
- llvm::ArrayRef<TableRow> Body;
-
-public:
- TableNode(TableRow H, llvm::ArrayRef<TableRow> B)
- : Node(NodeKind::NK_Table), Header(H), Body(B) {}
- const TableRow &getHeader() const { return Header; }
- llvm::ArrayRef<TableRow> getBody() const { return Body; }
- void dump() const {
- llvm::errs() << "TableNode: " << Header.Cells.size() << " header cells, "
- << Body.size() << " rows\n";
- }
- static bool classof(const Node *N) { return N->Kind == NodeKind::NK_Table; }
-};
-static_assert(std::is_trivially_destructible_v<TableNode>);
-
-class ListItemNode : public Node {
- llvm::ArrayRef<Node *> Children;
-
-public:
- explicit ListItemNode(llvm::ArrayRef<Node *> C)
- : Node(NodeKind::NK_ListItem), Children(C) {}
- llvm::ArrayRef<Node *> getChildren() const { return Children; }
- void dump() const {
- llvm::errs() << "ListItemNode (" << Children.size() << " children)\n";
+struct ListItemNode : BlockNode {
+ InlineList Children;
+ ListItemNode() : BlockNode(NodeKind::NK_ListItem) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "ListItemNode\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_ListItem;
}
};
-static_assert(std::is_trivially_destructible_v<ListItemNode>);
-class UnorderedListNode : public Node {
- llvm::ArrayRef<ListItemNode *> Items;
-
-public:
- UnorderedListNode() : Node(NodeKind::NK_UnorderedList), Items({}) {}
- explicit UnorderedListNode(llvm::ArrayRef<ListItemNode *> I)
- : Node(NodeKind::NK_UnorderedList), Items(I) {}
- llvm::ArrayRef<ListItemNode *> getItems() const { return Items; }
- void dump() const {
- llvm::errs() << "UnorderedListNode (" << Items.size() << " items)\n";
+struct UnorderedListNode : BlockNode {
+ llvm::simple_ilist<ListItemNode, llvm::ilist_sentinel_tracking<true>> Items;
+ UnorderedListNode() : BlockNode(NodeKind::NK_UnorderedList) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "UnorderedListNode\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_UnorderedList;
}
};
-static_assert(std::is_trivially_destructible_v<UnorderedListNode>);
-class OrderedListNode : public Node {
+struct OrderedListNode : BlockNode {
+private:
unsigned Start;
- llvm::ArrayRef<ListItemNode *> Items;
public:
- OrderedListNode(unsigned S, llvm::ArrayRef<ListItemNode *> I)
- : Node(NodeKind::NK_OrderedList), Start(S), Items(I) {}
+ llvm::simple_ilist<ListItemNode, llvm::ilist_sentinel_tracking<true>> Items;
+ explicit OrderedListNode(unsigned S = 1)
+ : BlockNode(NodeKind::NK_OrderedList), Start(S) {}
unsigned getStart() const { return Start; }
- llvm::ArrayRef<ListItemNode *> getItems() const { return Items; }
- void dump() const {
- llvm::errs() << "OrderedListNode: start=" << Start << " (" << Items.size()
- << " items)\n";
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "OrderedListNode: start=" << Start << "\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_OrderedList;
}
};
-static_assert(std::is_trivially_destructible_v<OrderedListNode>);
-class BlockQuoteNode : public Node {
- llvm::ArrayRef<Node *> Children;
-
-public:
- explicit BlockQuoteNode(llvm::ArrayRef<Node *> C)
- : Node(NodeKind::NK_BlockQuote), Children(C) {}
- llvm::ArrayRef<Node *> getChildren() const { return Children; }
- void dump() const {
- llvm::errs() << "BlockQuoteNode (" << Children.size() << " children)\n";
+struct BlockQuoteNode : BlockNode {
+ BlockList Children;
+ BlockQuoteNode() : BlockNode(NodeKind::NK_BlockQuote) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "BlockQuoteNode\n";
}
- static bool classof(const Node *N) {
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_BlockQuote;
}
};
-static_assert(std::is_trivially_destructible_v<BlockQuoteNode>);
-class ThematicBreakNode : public Node {
-public:
- ThematicBreakNode() : Node(NodeKind::NK_ThematicBreak) {}
- void dump() const { llvm::errs() << "ThematicBreakNode\n"; }
- static bool classof(const Node *N) {
+struct ThematicBreakNode : BlockNode {
+ ThematicBreakNode() : BlockNode(NodeKind::NK_ThematicBreak) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "ThematicBreakNode\n";
+ }
+ static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_ThematicBreak;
}
};
-static_assert(std::is_trivially_destructible_v<ThematicBreakNode>);
+
+struct DocumentNode : BlockNode {
+ BlockList Children;
+ DocumentNode() : BlockNode(NodeKind::NK_Document) {}
+ void dump(llvm::raw_ostream &OS = llvm::errs()) const {
+ OS << "DocumentNode\n";
+ }
+ static bool classof(const BlockNode *N) {
+ return N->Kind == NodeKind::NK_Document;
+ }
+};
+
+//===----------------------------------------------------------------------===//
+// ASTContext - owns the arena and string pool
+//===----------------------------------------------------------------------===//
+
+class ASTContext {
+ llvm::BumpPtrAllocator Arena;
+ llvm::StringSaver SSaver;
+ DocumentNode *Root = nullptr;
+
+public:
+ ASTContext() : SSaver(Arena) {}
+
+ template <typename T, typename... Args> T *allocate(Args &&...args) {
+ return new (Arena.Allocate<T>()) T(std::forward<Args>(args)...);
+ }
+
+ llvm::StringRef intern(llvm::StringRef S) { return SSaver.save(S); }
+ DocumentNode *getRoot() { return Root; }
+ void setRoot(DocumentNode *R) { Root = R; }
+};
} // namespace clang::doc::markdown
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
index 4b7d3e4b7bb4b..8621a980ec3ac 100644
--- a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -21,16 +21,14 @@ TEST(MarkdownNodeTest, TextNode) {
}
TEST(MarkdownNodeTest, FencedCodeNode) {
- StringRef Lines[] = {"int x = 0;", "int y = 1;", "return x + y;"};
- FencedCodeNode N("cpp", ArrayRef(Lines));
+ FencedCodeNode N("cpp", "int x = 0;\nint y = 1;\nreturn x + y;");
EXPECT_EQ(N.Kind, NodeKind::NK_FencedCode);
EXPECT_EQ(N.getLang(), "cpp");
- EXPECT_EQ(N.getLines().size(), 3u);
- EXPECT_EQ(N.getLines()[1], "int y = 1;");
+ EXPECT_EQ(N.getCode(), "int x = 0;\nint y = 1;\nreturn x + y;");
}
TEST(MarkdownNodeTest, HeadingNode) {
- HeadingNode N(2, {});
+ HeadingNode N(2);
EXPECT_EQ(N.Kind, NodeKind::NK_Heading);
EXPECT_EQ(N.getLevel(), 2u);
}
@@ -47,21 +45,21 @@ TEST(MarkdownNodeTest, InlineCodeNode) {
}
TEST(MarkdownNodeTest, EmphasisNode) {
- EmphasisNode N({});
+ EmphasisNode N;
EXPECT_EQ(N.Kind, NodeKind::NK_Emphasis);
- EXPECT_TRUE(N.getChildren().empty());
+ EXPECT_TRUE(N.Children.empty());
}
TEST(MarkdownNodeTest, UnorderedListNode) {
UnorderedListNode N;
EXPECT_EQ(N.Kind, NodeKind::NK_UnorderedList);
- EXPECT_TRUE(N.getItems().empty());
+ EXPECT_TRUE(N.Items.empty());
}
TEST(MarkdownNodeTest, ParagraphNode) {
- ParagraphNode N({});
+ ParagraphNode N;
EXPECT_EQ(N.Kind, NodeKind::NK_Paragraph);
- EXPECT_TRUE(N.getChildren().empty());
+ EXPECT_TRUE(N.Children.empty());
}
-} // namespace
\ No newline at end of file
+} // namespace
>From 03715b2d7447fdc224be105e70a0168c62a0dd25 Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Sat, 27 Jun 2026 14:49:44 -0400
Subject: [PATCH 5/7] [clang-doc] Address review feedback: print/dump in cpp,
remove sentinel tracking, restrict allocate
---
.../clang-doc/support/CMakeLists.txt | 1 +
.../clang-doc/support/Markdown.cpp | 177 ++++++++++++++++++
.../clang-doc/support/Markdown.h | 111 +++++------
.../unittests/clang-doc/CMakeLists.txt | 1 +
.../clang-doc/MarkdownParserTest.cpp | 12 +-
5 files changed, 238 insertions(+), 64 deletions(-)
create mode 100644 clang-tools-extra/clang-doc/support/Markdown.cpp
diff --git a/clang-tools-extra/clang-doc/support/CMakeLists.txt b/clang-tools-extra/clang-doc/support/CMakeLists.txt
index 8ac913ffbe998..7dc11f07ff8b3 100644
--- a/clang-tools-extra/clang-doc/support/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/support/CMakeLists.txt
@@ -6,5 +6,6 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangDocSupport STATIC
File.cpp
+ Markdown.cpp
Utils.cpp
)
diff --git a/clang-tools-extra/clang-doc/support/Markdown.cpp b/clang-tools-extra/clang-doc/support/Markdown.cpp
new file mode 100644
index 0000000000000..ad29ba4789ffb
--- /dev/null
+++ b/clang-tools-extra/clang-doc/support/Markdown.cpp
@@ -0,0 +1,177 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Markdown.h"
+#include "llvm/Support/Casting.h"
+
+namespace clang::doc::markdown {
+
+//===----------------------------------------------------------------------===//
+// Inline node print/dump
+//===----------------------------------------------------------------------===//
+
+void InlineNode::print(llvm::raw_ostream &OS) const {
+ switch (Kind) {
+ case NodeKind::NK_Text:
+ llvm::cast<TextNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_InlineCode:
+ llvm::cast<InlineCodeNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_Emphasis:
+ llvm::cast<EmphasisNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_Strong:
+ llvm::cast<StrongNode>(this)->print(OS);
+ break;
+ default:
+ OS << "UnknownInlineNode\n";
+ break;
+ }
+}
+
+LLVM_DUMP_METHOD void InlineNode::dump() const { print(llvm::errs()); }
+
+void TextNode::print(llvm::raw_ostream &OS) const {
+ OS << "TextNode: " << getText() << "\n";
+}
+
+LLVM_DUMP_METHOD void TextNode::dump() const { print(llvm::errs()); }
+
+void InlineCodeNode::print(llvm::raw_ostream &OS) const {
+ OS << "InlineCodeNode: " << getCode() << "\n";
+}
+
+LLVM_DUMP_METHOD void InlineCodeNode::dump() const { print(llvm::errs()); }
+
+void EmphasisNode::print(llvm::raw_ostream &OS) const {
+ OS << "EmphasisNode\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void EmphasisNode::dump() const { print(llvm::errs()); }
+
+void StrongNode::print(llvm::raw_ostream &OS) const {
+ OS << "StrongNode\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void StrongNode::dump() const { print(llvm::errs()); }
+
+//===----------------------------------------------------------------------===//
+// Block node print/dump
+//===----------------------------------------------------------------------===//
+
+void BlockNode::print(llvm::raw_ostream &OS) const {
+ switch (Kind) {
+ case NodeKind::NK_Paragraph:
+ llvm::cast<ParagraphNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_Heading:
+ llvm::cast<HeadingNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_FencedCode:
+ llvm::cast<FencedCodeNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_UnorderedList:
+ llvm::cast<UnorderedListNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_OrderedList:
+ llvm::cast<OrderedListNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_ListItem:
+ llvm::cast<ListItemNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_BlockQuote:
+ llvm::cast<BlockQuoteNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_ThematicBreak:
+ llvm::cast<ThematicBreakNode>(this)->print(OS);
+ break;
+ case NodeKind::NK_Document:
+ llvm::cast<DocumentNode>(this)->print(OS);
+ break;
+ default:
+ OS << "UnknownBlockNode\n";
+ break;
+ }
+}
+
+LLVM_DUMP_METHOD void BlockNode::dump() const { print(llvm::errs()); }
+
+void ParagraphNode::print(llvm::raw_ostream &OS) const {
+ OS << "ParagraphNode\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void ParagraphNode::dump() const { print(llvm::errs()); }
+
+void HeadingNode::print(llvm::raw_ostream &OS) const {
+ OS << "HeadingNode: level=" << getLevel() << "\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void HeadingNode::dump() const { print(llvm::errs()); }
+
+void FencedCodeNode::print(llvm::raw_ostream &OS) const {
+ OS << "FencedCodeNode: lang=" << getLang() << "\n" << getCode() << "\n";
+}
+
+LLVM_DUMP_METHOD void FencedCodeNode::dump() const { print(llvm::errs()); }
+
+void ListItemNode::print(llvm::raw_ostream &OS) const {
+ OS << "ListItemNode\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void ListItemNode::dump() const { print(llvm::errs()); }
+
+void UnorderedListNode::print(llvm::raw_ostream &OS) const {
+ OS << "UnorderedListNode\n";
+ for (const auto &Item : Items)
+ Item.print(OS);
+}
+
+LLVM_DUMP_METHOD void UnorderedListNode::dump() const { print(llvm::errs()); }
+
+void OrderedListNode::print(llvm::raw_ostream &OS) const {
+ OS << "OrderedListNode: start=" << getStart() << "\n";
+ for (const auto &Item : Items)
+ Item.print(OS);
+}
+
+LLVM_DUMP_METHOD void OrderedListNode::dump() const { print(llvm::errs()); }
+
+void BlockQuoteNode::print(llvm::raw_ostream &OS) const {
+ OS << "BlockQuoteNode\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void BlockQuoteNode::dump() const { print(llvm::errs()); }
+
+void ThematicBreakNode::print(llvm::raw_ostream &OS) const {
+ OS << "ThematicBreakNode\n";
+}
+
+LLVM_DUMP_METHOD void ThematicBreakNode::dump() const { print(llvm::errs()); }
+
+void DocumentNode::print(llvm::raw_ostream &OS) const {
+ OS << "DocumentNode\n";
+ for (const auto &Child : Children)
+ Child.print(OS);
+}
+
+LLVM_DUMP_METHOD void DocumentNode::dump() const { print(llvm::errs()); }
+
+} // namespace clang::doc::markdown
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
index 1920b7a08b8fe..410f133b0e74d 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.h
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -13,7 +13,7 @@
#include "llvm/ADT/simple_ilist.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Casting.h"
-#include "llvm/Support/StringSaver.h"
+#include "llvm/Support/Compiler.h"
#include "llvm/Support/raw_ostream.h"
#include <type_traits>
@@ -38,7 +38,6 @@ enum class NodeKind {
NK_Document,
};
-// Forward declarations
struct InlineNode;
struct BlockNode;
@@ -46,13 +45,15 @@ struct BlockNode;
// Inline nodes
//===----------------------------------------------------------------------===//
-struct InlineNode
- : llvm::ilist_node<InlineNode, llvm::ilist_sentinel_tracking<true>> {
+struct InlineNode : llvm::ilist_node<InlineNode> {
NodeKind Kind;
explicit InlineNode(NodeKind K) : Kind(K) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const;
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
};
+using InlineList = llvm::simple_ilist<InlineNode>;
+
struct TextNode : InlineNode {
private:
llvm::StringRef Text;
@@ -61,9 +62,8 @@ struct TextNode : InlineNode {
explicit TextNode(llvm::StringRef T)
: InlineNode(NodeKind::NK_Text), Text(T) {}
llvm::StringRef getText() const { return Text; }
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "TextNode: " << Text << "\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Text;
}
@@ -78,9 +78,8 @@ struct InlineCodeNode : InlineNode {
explicit InlineCodeNode(llvm::StringRef C)
: InlineNode(NodeKind::NK_InlineCode), Code(C) {}
llvm::StringRef getCode() const { return Code; }
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "InlineCodeNode: " << Code << "\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_InlineCode;
}
@@ -88,22 +87,20 @@ struct InlineCodeNode : InlineNode {
static_assert(std::is_trivially_destructible_v<InlineCodeNode>);
struct EmphasisNode : InlineNode {
- llvm::simple_ilist<InlineNode, llvm::ilist_sentinel_tracking<true>> Children;
+ InlineList Children;
EmphasisNode() : InlineNode(NodeKind::NK_Emphasis) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "EmphasisNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Emphasis;
}
};
struct StrongNode : InlineNode {
- llvm::simple_ilist<InlineNode, llvm::ilist_sentinel_tracking<true>> Children;
+ InlineList Children;
StrongNode() : InlineNode(NodeKind::NK_Strong) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "StrongNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Strong;
}
@@ -113,24 +110,20 @@ struct StrongNode : InlineNode {
// Block nodes
//===----------------------------------------------------------------------===//
-struct BlockNode
- : llvm::ilist_node<BlockNode, llvm::ilist_sentinel_tracking<true>> {
+struct BlockNode : llvm::ilist_node<BlockNode> {
NodeKind Kind;
explicit BlockNode(NodeKind K) : Kind(K) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const;
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
};
-using InlineList =
- llvm::simple_ilist<InlineNode, llvm::ilist_sentinel_tracking<true>>;
-using BlockList =
- llvm::simple_ilist<BlockNode, llvm::ilist_sentinel_tracking<true>>;
+using BlockList = llvm::simple_ilist<BlockNode>;
struct ParagraphNode : BlockNode {
InlineList Children;
ParagraphNode() : BlockNode(NodeKind::NK_Paragraph) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "ParagraphNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Paragraph;
}
@@ -145,9 +138,8 @@ struct HeadingNode : BlockNode {
explicit HeadingNode(unsigned L)
: BlockNode(NodeKind::NK_Heading), Level(L) {}
unsigned getLevel() const { return Level; }
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "HeadingNode: level=" << Level << "\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Heading;
}
@@ -163,32 +155,29 @@ struct FencedCodeNode : BlockNode {
: BlockNode(NodeKind::NK_FencedCode), Lang(L), Code(C) {}
llvm::StringRef getLang() const { return Lang; }
llvm::StringRef getCode() const { return Code; }
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "FencedCodeNode: lang=" << Lang << "\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_FencedCode;
}
};
static_assert(std::is_trivially_destructible_v<FencedCodeNode>);
-struct ListItemNode : BlockNode {
+struct ListItemNode : BlockNode, llvm::ilist_node<ListItemNode> {
InlineList Children;
ListItemNode() : BlockNode(NodeKind::NK_ListItem) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "ListItemNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_ListItem;
}
};
struct UnorderedListNode : BlockNode {
- llvm::simple_ilist<ListItemNode, llvm::ilist_sentinel_tracking<true>> Items;
+ llvm::simple_ilist<ListItemNode> Items;
UnorderedListNode() : BlockNode(NodeKind::NK_UnorderedList) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "UnorderedListNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_UnorderedList;
}
@@ -199,13 +188,12 @@ struct OrderedListNode : BlockNode {
unsigned Start;
public:
- llvm::simple_ilist<ListItemNode, llvm::ilist_sentinel_tracking<true>> Items;
+ llvm::simple_ilist<ListItemNode> Items;
explicit OrderedListNode(unsigned S = 1)
: BlockNode(NodeKind::NK_OrderedList), Start(S) {}
unsigned getStart() const { return Start; }
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "OrderedListNode: start=" << Start << "\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_OrderedList;
}
@@ -214,9 +202,8 @@ struct OrderedListNode : BlockNode {
struct BlockQuoteNode : BlockNode {
BlockList Children;
BlockQuoteNode() : BlockNode(NodeKind::NK_BlockQuote) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "BlockQuoteNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_BlockQuote;
}
@@ -224,42 +211,44 @@ struct BlockQuoteNode : BlockNode {
struct ThematicBreakNode : BlockNode {
ThematicBreakNode() : BlockNode(NodeKind::NK_ThematicBreak) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "ThematicBreakNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_ThematicBreak;
}
};
struct DocumentNode : BlockNode {
+ // FIXME: add constructor that accepts children once parser is in place
BlockList Children;
DocumentNode() : BlockNode(NodeKind::NK_Document) {}
- void dump(llvm::raw_ostream &OS = llvm::errs()) const {
- OS << "DocumentNode\n";
- }
+ void print(llvm::raw_ostream &OS) const;
+ LLVM_DUMP_METHOD void dump() const;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Document;
}
};
//===----------------------------------------------------------------------===//
-// ASTContext - owns the arena and string pool
+// ASTContext - owns the arena
//===----------------------------------------------------------------------===//
+template <typename T>
+using IsMarkdownNode = std::enable_if_t<std::is_base_of_v<InlineNode, T> ||
+ std::is_base_of_v<BlockNode, T>>;
+
class ASTContext {
llvm::BumpPtrAllocator Arena;
- llvm::StringSaver SSaver;
DocumentNode *Root = nullptr;
public:
- ASTContext() : SSaver(Arena) {}
+ ASTContext() = default;
- template <typename T, typename... Args> T *allocate(Args &&...args) {
+ template <typename T, typename... Args, typename = IsMarkdownNode<T>>
+ T *allocate(Args &&...args) {
return new (Arena.Allocate<T>()) T(std::forward<Args>(args)...);
}
- llvm::StringRef intern(llvm::StringRef S) { return SSaver.save(S); }
DocumentNode *getRoot() { return Root; }
void setRoot(DocumentNode *R) { Root = R; }
};
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 688a547a7f031..935df6da8ac78 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -50,5 +50,6 @@ clang_target_link_libraries(ClangDocTests
target_link_libraries(ClangDocTests
PRIVATE
clangDoc
+ clangDocSupport
LLVMTestingSupport
)
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
index 8621a980ec3ac..1b99776e7b6eb 100644
--- a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -21,10 +21,13 @@ TEST(MarkdownNodeTest, TextNode) {
}
TEST(MarkdownNodeTest, FencedCodeNode) {
- FencedCodeNode N("cpp", "int x = 0;\nint y = 1;\nreturn x + y;");
+ FencedCodeNode N("cpp", R"(int x = 0;
+int y = 1;
+return x + y;)");
EXPECT_EQ(N.Kind, NodeKind::NK_FencedCode);
EXPECT_EQ(N.getLang(), "cpp");
- EXPECT_EQ(N.getCode(), "int x = 0;\nint y = 1;\nreturn x + y;");
+ EXPECT_TRUE(N.getCode().contains("int x = 0;"));
+ EXPECT_TRUE(N.getCode().contains("int y = 1;"));
}
TEST(MarkdownNodeTest, HeadingNode) {
@@ -46,8 +49,11 @@ TEST(MarkdownNodeTest, InlineCodeNode) {
TEST(MarkdownNodeTest, EmphasisNode) {
EmphasisNode N;
+ TextNode Child("emphasized");
+ N.Children.push_back(Child);
EXPECT_EQ(N.Kind, NodeKind::NK_Emphasis);
- EXPECT_TRUE(N.Children.empty());
+ EXPECT_FALSE(N.Children.empty());
+ EXPECT_EQ(llvm::cast<TextNode>(N.Children.front()).getText(), "emphasized");
}
TEST(MarkdownNodeTest, UnorderedListNode) {
>From 6678ba4fad1ab38c944953a6df4b6e5444e29c1c Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Mon, 29 Jun 2026 01:25:13 -0400
Subject: [PATCH 6/7] [clang-doc] Clean up: node definitions only, no parser,
no ASTContext
---
.../clang-doc/support/CMakeLists.txt | 8 +-
.../clang-doc/support/Markdown.cpp | 80 ------------
.../clang-doc/support/Markdown.h | 119 ++++++++----------
.../unittests/clang-doc/CMakeLists.txt | 2 +-
.../clang-doc/MarkdownParserTest.cpp | 29 ++++-
5 files changed, 88 insertions(+), 150 deletions(-)
diff --git a/clang-tools-extra/clang-doc/support/CMakeLists.txt b/clang-tools-extra/clang-doc/support/CMakeLists.txt
index 7dc11f07ff8b3..2ea459b8a9404 100644
--- a/clang-tools-extra/clang-doc/support/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/support/CMakeLists.txt
@@ -5,7 +5,13 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangDocSupport STATIC
+ PARTIAL_SOURCES_INTENDED
File.cpp
- Markdown.cpp
Utils.cpp
)
+
+add_clang_library(clangDocMarkdown STATIC
+ PARTIAL_SOURCES_INTENDED
+ Markdown.cpp
+ )
+
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/support/Markdown.cpp b/clang-tools-extra/clang-doc/support/Markdown.cpp
index ad29ba4789ffb..ec81db39e70f1 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.cpp
+++ b/clang-tools-extra/clang-doc/support/Markdown.cpp
@@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//
#include "Markdown.h"
-#include "llvm/Support/Casting.h"
namespace clang::doc::markdown {
@@ -15,95 +14,32 @@ namespace clang::doc::markdown {
// Inline node print/dump
//===----------------------------------------------------------------------===//
-void InlineNode::print(llvm::raw_ostream &OS) const {
- switch (Kind) {
- case NodeKind::NK_Text:
- llvm::cast<TextNode>(this)->print(OS);
- break;
- case NodeKind::NK_InlineCode:
- llvm::cast<InlineCodeNode>(this)->print(OS);
- break;
- case NodeKind::NK_Emphasis:
- llvm::cast<EmphasisNode>(this)->print(OS);
- break;
- case NodeKind::NK_Strong:
- llvm::cast<StrongNode>(this)->print(OS);
- break;
- default:
- OS << "UnknownInlineNode\n";
- break;
- }
-}
-
LLVM_DUMP_METHOD void InlineNode::dump() const { print(llvm::errs()); }
void TextNode::print(llvm::raw_ostream &OS) const {
OS << "TextNode: " << getText() << "\n";
}
-LLVM_DUMP_METHOD void TextNode::dump() const { print(llvm::errs()); }
-
void InlineCodeNode::print(llvm::raw_ostream &OS) const {
OS << "InlineCodeNode: " << getCode() << "\n";
}
-LLVM_DUMP_METHOD void InlineCodeNode::dump() const { print(llvm::errs()); }
-
void EmphasisNode::print(llvm::raw_ostream &OS) const {
OS << "EmphasisNode\n";
for (const auto &Child : Children)
Child.print(OS);
}
-LLVM_DUMP_METHOD void EmphasisNode::dump() const { print(llvm::errs()); }
-
void StrongNode::print(llvm::raw_ostream &OS) const {
OS << "StrongNode\n";
for (const auto &Child : Children)
Child.print(OS);
}
-LLVM_DUMP_METHOD void StrongNode::dump() const { print(llvm::errs()); }
-
//===----------------------------------------------------------------------===//
// Block node print/dump
//===----------------------------------------------------------------------===//
-void BlockNode::print(llvm::raw_ostream &OS) const {
- switch (Kind) {
- case NodeKind::NK_Paragraph:
- llvm::cast<ParagraphNode>(this)->print(OS);
- break;
- case NodeKind::NK_Heading:
- llvm::cast<HeadingNode>(this)->print(OS);
- break;
- case NodeKind::NK_FencedCode:
- llvm::cast<FencedCodeNode>(this)->print(OS);
- break;
- case NodeKind::NK_UnorderedList:
- llvm::cast<UnorderedListNode>(this)->print(OS);
- break;
- case NodeKind::NK_OrderedList:
- llvm::cast<OrderedListNode>(this)->print(OS);
- break;
- case NodeKind::NK_ListItem:
- llvm::cast<ListItemNode>(this)->print(OS);
- break;
- case NodeKind::NK_BlockQuote:
- llvm::cast<BlockQuoteNode>(this)->print(OS);
- break;
- case NodeKind::NK_ThematicBreak:
- llvm::cast<ThematicBreakNode>(this)->print(OS);
- break;
- case NodeKind::NK_Document:
- llvm::cast<DocumentNode>(this)->print(OS);
- break;
- default:
- OS << "UnknownBlockNode\n";
- break;
- }
-}
-
LLVM_DUMP_METHOD void BlockNode::dump() const { print(llvm::errs()); }
void ParagraphNode::print(llvm::raw_ostream &OS) const {
@@ -112,22 +48,16 @@ void ParagraphNode::print(llvm::raw_ostream &OS) const {
Child.print(OS);
}
-LLVM_DUMP_METHOD void ParagraphNode::dump() const { print(llvm::errs()); }
-
void HeadingNode::print(llvm::raw_ostream &OS) const {
OS << "HeadingNode: level=" << getLevel() << "\n";
for (const auto &Child : Children)
Child.print(OS);
}
-LLVM_DUMP_METHOD void HeadingNode::dump() const { print(llvm::errs()); }
-
void FencedCodeNode::print(llvm::raw_ostream &OS) const {
OS << "FencedCodeNode: lang=" << getLang() << "\n" << getCode() << "\n";
}
-LLVM_DUMP_METHOD void FencedCodeNode::dump() const { print(llvm::errs()); }
-
void ListItemNode::print(llvm::raw_ostream &OS) const {
OS << "ListItemNode\n";
for (const auto &Child : Children)
@@ -142,36 +72,26 @@ void UnorderedListNode::print(llvm::raw_ostream &OS) const {
Item.print(OS);
}
-LLVM_DUMP_METHOD void UnorderedListNode::dump() const { print(llvm::errs()); }
-
void OrderedListNode::print(llvm::raw_ostream &OS) const {
OS << "OrderedListNode: start=" << getStart() << "\n";
for (const auto &Item : Items)
Item.print(OS);
}
-LLVM_DUMP_METHOD void OrderedListNode::dump() const { print(llvm::errs()); }
-
void BlockQuoteNode::print(llvm::raw_ostream &OS) const {
OS << "BlockQuoteNode\n";
for (const auto &Child : Children)
Child.print(OS);
}
-LLVM_DUMP_METHOD void BlockQuoteNode::dump() const { print(llvm::errs()); }
-
void ThematicBreakNode::print(llvm::raw_ostream &OS) const {
OS << "ThematicBreakNode\n";
}
-LLVM_DUMP_METHOD void ThematicBreakNode::dump() const { print(llvm::errs()); }
-
void DocumentNode::print(llvm::raw_ostream &OS) const {
OS << "DocumentNode\n";
for (const auto &Child : Children)
Child.print(OS);
}
-LLVM_DUMP_METHOD void DocumentNode::dump() const { print(llvm::errs()); }
-
} // namespace clang::doc::markdown
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
index 410f133b0e74d..57f3eaf887c44 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.h
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -5,17 +5,27 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Defines the Markdown AST node hierarchy for the clang-doc Markdown parser.
+///
+/// Block nodes represent structural constructs (paragraphs, headings, lists,
+/// fenced code blocks, etc). Inline nodes represent span-level content (text,
+/// emphasis, inline code) that appears inside block nodes.
+///
+/// All nodes are arena-allocated via ASTContext, which owns their lifetime.
+/// The parser builds the tree by calling push_back() on simple_ilist members.
+///
+//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/simple_ilist.h"
-#include "llvm/Support/Allocator.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/raw_ostream.h"
-#include <type_traits>
namespace clang::doc::markdown {
@@ -29,10 +39,9 @@ enum class NodeKind {
NK_Paragraph,
NK_Heading,
NK_FencedCode,
- NK_Table,
+ NK_Table, // TODO: add TableNode
NK_UnorderedList,
NK_OrderedList,
- NK_ListItem,
NK_BlockQuote,
NK_ThematicBreak,
NK_Document,
@@ -42,18 +51,23 @@ struct InlineNode;
struct BlockNode;
//===----------------------------------------------------------------------===//
-// Inline nodes
+// Inline nodes -- span-level content inside block nodes
//===----------------------------------------------------------------------===//
+/// Base class for all inline nodes. Inline nodes represent span-level content
+/// such as text, emphasis, and inline code. They live in InlineList members
+/// of block nodes.
struct InlineNode : llvm::ilist_node<InlineNode> {
NodeKind Kind;
explicit InlineNode(NodeKind K) : Kind(K) {}
- void print(llvm::raw_ostream &OS) const;
+ virtual ~InlineNode() = default;
+ virtual void print(llvm::raw_ostream &OS) const = 0;
LLVM_DUMP_METHOD void dump() const;
};
using InlineList = llvm::simple_ilist<InlineNode>;
+/// A plain text run.
struct TextNode : InlineNode {
private:
llvm::StringRef Text;
@@ -62,14 +76,13 @@ struct TextNode : InlineNode {
explicit TextNode(llvm::StringRef T)
: InlineNode(NodeKind::NK_Text), Text(T) {}
llvm::StringRef getText() const { return Text; }
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Text;
}
};
-static_assert(std::is_trivially_destructible_v<TextNode>);
+/// A backtick-delimited inline code span.
struct InlineCodeNode : InlineNode {
private:
llvm::StringRef Code;
@@ -78,57 +91,60 @@ struct InlineCodeNode : InlineNode {
explicit InlineCodeNode(llvm::StringRef C)
: InlineNode(NodeKind::NK_InlineCode), Code(C) {}
llvm::StringRef getCode() const { return Code; }
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_InlineCode;
}
};
-static_assert(std::is_trivially_destructible_v<InlineCodeNode>);
+/// An emphasis span (* or _).
struct EmphasisNode : InlineNode {
InlineList Children;
EmphasisNode() : InlineNode(NodeKind::NK_Emphasis) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Emphasis;
}
};
+/// A strong emphasis span (** or __).
struct StrongNode : InlineNode {
InlineList Children;
StrongNode() : InlineNode(NodeKind::NK_Strong) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
return N->Kind == NodeKind::NK_Strong;
}
};
//===----------------------------------------------------------------------===//
-// Block nodes
+// Block nodes -- structural constructs
//===----------------------------------------------------------------------===//
+/// Base class for all block nodes. Block nodes represent structural constructs
+/// such as paragraphs, headings, and lists. They live in BlockList members of
+/// container block nodes.
struct BlockNode : llvm::ilist_node<BlockNode> {
NodeKind Kind;
explicit BlockNode(NodeKind K) : Kind(K) {}
- void print(llvm::raw_ostream &OS) const;
+ virtual ~BlockNode() = default;
+ virtual void print(llvm::raw_ostream &OS) const = 0;
LLVM_DUMP_METHOD void dump() const;
};
using BlockList = llvm::simple_ilist<BlockNode>;
+/// A paragraph of inline content.
struct ParagraphNode : BlockNode {
InlineList Children;
ParagraphNode() : BlockNode(NodeKind::NK_Paragraph) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Paragraph;
}
};
+/// An ATX heading (# through ######).
struct HeadingNode : BlockNode {
private:
unsigned Level;
@@ -138,13 +154,13 @@ struct HeadingNode : BlockNode {
explicit HeadingNode(unsigned L)
: BlockNode(NodeKind::NK_Heading), Level(L) {}
unsigned getLevel() const { return Level; }
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Heading;
}
};
+/// A fenced code block (``` or ~~~). Lang holds the info string.
struct FencedCodeNode : BlockNode {
private:
llvm::StringRef Lang;
@@ -155,34 +171,32 @@ struct FencedCodeNode : BlockNode {
: BlockNode(NodeKind::NK_FencedCode), Lang(L), Code(C) {}
llvm::StringRef getLang() const { return Lang; }
llvm::StringRef getCode() const { return Code; }
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_FencedCode;
}
};
-static_assert(std::is_trivially_destructible_v<FencedCodeNode>);
-struct ListItemNode : BlockNode, llvm::ilist_node<ListItemNode> {
+/// A single item in an unordered or ordered list.
+/// ListItemNode is not a BlockNode -- it only lives inside list nodes.
+struct ListItemNode : llvm::ilist_node<ListItemNode> {
InlineList Children;
- ListItemNode() : BlockNode(NodeKind::NK_ListItem) {}
+ ListItemNode() = default;
void print(llvm::raw_ostream &OS) const;
LLVM_DUMP_METHOD void dump() const;
- static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_ListItem;
- }
};
+/// An unordered list (-, *, or + markers).
struct UnorderedListNode : BlockNode {
llvm::simple_ilist<ListItemNode> Items;
UnorderedListNode() : BlockNode(NodeKind::NK_UnorderedList) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_UnorderedList;
}
};
+/// An ordered list (1. 2. 3. markers). Start holds the first item number.
struct OrderedListNode : BlockNode {
private:
unsigned Start;
@@ -192,67 +206,42 @@ struct OrderedListNode : BlockNode {
explicit OrderedListNode(unsigned S = 1)
: BlockNode(NodeKind::NK_OrderedList), Start(S) {}
unsigned getStart() const { return Start; }
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_OrderedList;
}
};
+/// A block quote (> marker).
struct BlockQuoteNode : BlockNode {
BlockList Children;
BlockQuoteNode() : BlockNode(NodeKind::NK_BlockQuote) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_BlockQuote;
}
};
+/// A thematic break (---, ***, or ___).
struct ThematicBreakNode : BlockNode {
ThematicBreakNode() : BlockNode(NodeKind::NK_ThematicBreak) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_ThematicBreak;
}
};
+/// The root document node. Contains all top-level block nodes.
+/// Children are added by the parser via push_back on the Children ilist.
struct DocumentNode : BlockNode {
- // FIXME: add constructor that accepts children once parser is in place
BlockList Children;
DocumentNode() : BlockNode(NodeKind::NK_Document) {}
- void print(llvm::raw_ostream &OS) const;
- LLVM_DUMP_METHOD void dump() const;
+ void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
return N->Kind == NodeKind::NK_Document;
}
};
-//===----------------------------------------------------------------------===//
-// ASTContext - owns the arena
-//===----------------------------------------------------------------------===//
-
-template <typename T>
-using IsMarkdownNode = std::enable_if_t<std::is_base_of_v<InlineNode, T> ||
- std::is_base_of_v<BlockNode, T>>;
-
-class ASTContext {
- llvm::BumpPtrAllocator Arena;
- DocumentNode *Root = nullptr;
-
-public:
- ASTContext() = default;
-
- template <typename T, typename... Args, typename = IsMarkdownNode<T>>
- T *allocate(Args &&...args) {
- return new (Arena.Allocate<T>()) T(std::forward<Args>(args)...);
- }
-
- DocumentNode *getRoot() { return Root; }
- void setRoot(DocumentNode *R) { Root = R; }
-};
-
} // namespace clang::doc::markdown
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SUPPORT_MARKDOWN_H
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 935df6da8ac78..97a26f0e24d09 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -50,6 +50,6 @@ clang_target_link_libraries(ClangDocTests
target_link_libraries(ClangDocTests
PRIVATE
clangDoc
- clangDocSupport
+ clangDocMarkdown
LLVMTestingSupport
)
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
index 1b99776e7b6eb..c8871ee4d5e8e 100644
--- a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -21,9 +21,7 @@ TEST(MarkdownNodeTest, TextNode) {
}
TEST(MarkdownNodeTest, FencedCodeNode) {
- FencedCodeNode N("cpp", R"(int x = 0;
-int y = 1;
-return x + y;)");
+ FencedCodeNode N("cpp", "int x = 0;\nint y = 1;");
EXPECT_EQ(N.Kind, NodeKind::NK_FencedCode);
EXPECT_EQ(N.getLang(), "cpp");
EXPECT_TRUE(N.getCode().contains("int x = 0;"));
@@ -68,4 +66,29 @@ TEST(MarkdownNodeTest, ParagraphNode) {
EXPECT_TRUE(N.Children.empty());
}
+TEST(MarkdownNodeTest, DocumentNode) {
+ DocumentNode N;
+ EXPECT_EQ(N.Kind, NodeKind::NK_Document);
+ EXPECT_TRUE(N.Children.empty());
+}
+
+TEST(MarkdownNodeTest, ParagraphWithChildren) {
+ ParagraphNode Para;
+ TextNode Child("hello");
+ Para.Children.push_back(Child);
+ EXPECT_FALSE(Para.Children.empty());
+ EXPECT_EQ(llvm::cast<TextNode>(Para.Children.front()).getText(), "hello");
+}
+
+TEST(MarkdownNodeTest, UnorderedListWithItems) {
+ UnorderedListNode List;
+ ListItemNode Item;
+ TextNode Child("item text");
+ Item.Children.push_back(Child);
+ List.Items.push_back(Item);
+ EXPECT_FALSE(List.Items.empty());
+ EXPECT_EQ(llvm::cast<TextNode>(List.Items.front().Children.front()).getText(),
+ "item text");
+}
+
} // namespace
>From 750aab5d85cc611adbcff3b4089f9e112c198d7f Mon Sep 17 00:00:00 2001
From: Neil-N4 <neilnair4 at gmail.com>
Date: Mon, 29 Jun 2026 22:59:02 -0400
Subject: [PATCH 7/7] [clang-doc] Convert nodes to classes with
insert/remove/iterate methods
---
.../clang-doc/support/Markdown.cpp | 26 +--
.../clang-doc/support/Markdown.h | 202 ++++++++++++------
.../clang-doc/MarkdownParserTest.cpp | 74 ++++---
3 files changed, 189 insertions(+), 113 deletions(-)
diff --git a/clang-tools-extra/clang-doc/support/Markdown.cpp b/clang-tools-extra/clang-doc/support/Markdown.cpp
index ec81db39e70f1..35984a0264bc7 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.cpp
+++ b/clang-tools-extra/clang-doc/support/Markdown.cpp
@@ -10,10 +10,6 @@
namespace clang::doc::markdown {
-//===----------------------------------------------------------------------===//
-// Inline node print/dump
-//===----------------------------------------------------------------------===//
-
LLVM_DUMP_METHOD void InlineNode::dump() const { print(llvm::errs()); }
void TextNode::print(llvm::raw_ostream &OS) const {
@@ -26,31 +22,27 @@ void InlineCodeNode::print(llvm::raw_ostream &OS) const {
void EmphasisNode::print(llvm::raw_ostream &OS) const {
OS << "EmphasisNode\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
void StrongNode::print(llvm::raw_ostream &OS) const {
OS << "StrongNode\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
-//===----------------------------------------------------------------------===//
-// Block node print/dump
-//===----------------------------------------------------------------------===//
-
LLVM_DUMP_METHOD void BlockNode::dump() const { print(llvm::errs()); }
void ParagraphNode::print(llvm::raw_ostream &OS) const {
OS << "ParagraphNode\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
void HeadingNode::print(llvm::raw_ostream &OS) const {
OS << "HeadingNode: level=" << getLevel() << "\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
@@ -60,7 +52,7 @@ void FencedCodeNode::print(llvm::raw_ostream &OS) const {
void ListItemNode::print(llvm::raw_ostream &OS) const {
OS << "ListItemNode\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
@@ -68,19 +60,19 @@ LLVM_DUMP_METHOD void ListItemNode::dump() const { print(llvm::errs()); }
void UnorderedListNode::print(llvm::raw_ostream &OS) const {
OS << "UnorderedListNode\n";
- for (const auto &Item : Items)
+ for (const auto &Item : items())
Item.print(OS);
}
void OrderedListNode::print(llvm::raw_ostream &OS) const {
OS << "OrderedListNode: start=" << getStart() << "\n";
- for (const auto &Item : Items)
+ for (const auto &Item : items())
Item.print(OS);
}
void BlockQuoteNode::print(llvm::raw_ostream &OS) const {
OS << "BlockQuoteNode\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
@@ -90,7 +82,7 @@ void ThematicBreakNode::print(llvm::raw_ostream &OS) const {
void DocumentNode::print(llvm::raw_ostream &OS) const {
OS << "DocumentNode\n";
- for (const auto &Child : Children)
+ for (const auto &Child : children())
Child.print(OS);
}
diff --git a/clang-tools-extra/clang-doc/support/Markdown.h b/clang-tools-extra/clang-doc/support/Markdown.h
index 57f3eaf887c44..9bc621d362a5e 100644
--- a/clang-tools-extra/clang-doc/support/Markdown.h
+++ b/clang-tools-extra/clang-doc/support/Markdown.h
@@ -13,8 +13,7 @@
/// fenced code blocks, etc). Inline nodes represent span-level content (text,
/// emphasis, inline code) that appears inside block nodes.
///
-/// All nodes are arena-allocated via ASTContext, which owns their lifetime.
-/// The parser builds the tree by calling push_back() on simple_ilist members.
+/// All nodes are arena-allocated via ASTContext, which manages their lifetime.
///
//===----------------------------------------------------------------------===//
@@ -47,125 +46,148 @@ enum class NodeKind {
NK_Document,
};
-struct InlineNode;
-struct BlockNode;
-
-//===----------------------------------------------------------------------===//
-// Inline nodes -- span-level content inside block nodes
-//===----------------------------------------------------------------------===//
+class InlineNode;
+class BlockNode;
/// Base class for all inline nodes. Inline nodes represent span-level content
-/// such as text, emphasis, and inline code. They live in InlineList members
-/// of block nodes.
-struct InlineNode : llvm::ilist_node<InlineNode> {
- NodeKind Kind;
+/// such as text, emphasis, and inline code.
+class InlineNode : public llvm::ilist_node<InlineNode> {
+public:
explicit InlineNode(NodeKind K) : Kind(K) {}
virtual ~InlineNode() = default;
+ NodeKind getKind() const { return Kind; }
+ /// Recursively prints the node and its children to OS.
virtual void print(llvm::raw_ostream &OS) const = 0;
+ /// Prints to llvm::errs(). Only available in assert builds.
LLVM_DUMP_METHOD void dump() const;
+
+private:
+ NodeKind Kind;
};
using InlineList = llvm::simple_ilist<InlineNode>;
/// A plain text run.
-struct TextNode : InlineNode {
-private:
- llvm::StringRef Text;
-
+class TextNode : public InlineNode {
public:
explicit TextNode(llvm::StringRef T)
: InlineNode(NodeKind::NK_Text), Text(T) {}
llvm::StringRef getText() const { return Text; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
- return N->Kind == NodeKind::NK_Text;
+ return N->getKind() == NodeKind::NK_Text;
}
-};
-/// A backtick-delimited inline code span.
-struct InlineCodeNode : InlineNode {
private:
- llvm::StringRef Code;
+ llvm::StringRef Text;
+};
+/// A backtick-delimited inline code span.
+class InlineCodeNode : public InlineNode {
public:
explicit InlineCodeNode(llvm::StringRef C)
: InlineNode(NodeKind::NK_InlineCode), Code(C) {}
llvm::StringRef getCode() const { return Code; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
- return N->Kind == NodeKind::NK_InlineCode;
+ return N->getKind() == NodeKind::NK_InlineCode;
}
+
+private:
+ llvm::StringRef Code;
};
/// An emphasis span (* or _).
-struct EmphasisNode : InlineNode {
- InlineList Children;
+class EmphasisNode : public InlineNode {
+public:
EmphasisNode() : InlineNode(NodeKind::NK_Emphasis) {}
+ void addChild(InlineNode &N) { Children.push_back(N); }
+ void removeChild(InlineNode &N) { Children.remove(N); }
+ InlineList &children() { return Children; }
+ const InlineList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
- return N->Kind == NodeKind::NK_Emphasis;
+ return N->getKind() == NodeKind::NK_Emphasis;
}
+
+private:
+ InlineList Children;
};
/// A strong emphasis span (** or __).
-struct StrongNode : InlineNode {
- InlineList Children;
+class StrongNode : public InlineNode {
+public:
StrongNode() : InlineNode(NodeKind::NK_Strong) {}
+ void addChild(InlineNode &N) { Children.push_back(N); }
+ void removeChild(InlineNode &N) { Children.remove(N); }
+ InlineList &children() { return Children; }
+ const InlineList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const InlineNode *N) {
- return N->Kind == NodeKind::NK_Strong;
+ return N->getKind() == NodeKind::NK_Strong;
}
-};
-//===----------------------------------------------------------------------===//
-// Block nodes -- structural constructs
-//===----------------------------------------------------------------------===//
+private:
+ InlineList Children;
+};
/// Base class for all block nodes. Block nodes represent structural constructs
-/// such as paragraphs, headings, and lists. They live in BlockList members of
-/// container block nodes.
-struct BlockNode : llvm::ilist_node<BlockNode> {
- NodeKind Kind;
+/// such as paragraphs, headings, and lists.
+class BlockNode : public llvm::ilist_node<BlockNode> {
+public:
explicit BlockNode(NodeKind K) : Kind(K) {}
virtual ~BlockNode() = default;
+ NodeKind getKind() const { return Kind; }
+ /// Recursively prints the node and its children to OS.
virtual void print(llvm::raw_ostream &OS) const = 0;
+ /// Prints to llvm::errs(). Only available in assert builds.
LLVM_DUMP_METHOD void dump() const;
+
+private:
+ NodeKind Kind;
};
using BlockList = llvm::simple_ilist<BlockNode>;
/// A paragraph of inline content.
-struct ParagraphNode : BlockNode {
- InlineList Children;
+class ParagraphNode : public BlockNode {
+public:
ParagraphNode() : BlockNode(NodeKind::NK_Paragraph) {}
+ void addChild(InlineNode &N) { Children.push_back(N); }
+ void removeChild(InlineNode &N) { Children.remove(N); }
+ InlineList &children() { return Children; }
+ const InlineList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_Paragraph;
+ return N->getKind() == NodeKind::NK_Paragraph;
}
-};
-/// An ATX heading (# through ######).
-struct HeadingNode : BlockNode {
private:
- unsigned Level;
+ InlineList Children;
+};
+/// An ATX heading (# through ######).
+class HeadingNode : public BlockNode {
public:
- InlineList Children;
explicit HeadingNode(unsigned L)
: BlockNode(NodeKind::NK_Heading), Level(L) {}
unsigned getLevel() const { return Level; }
+ void addChild(InlineNode &N) { Children.push_back(N); }
+ void removeChild(InlineNode &N) { Children.remove(N); }
+ InlineList &children() { return Children; }
+ const InlineList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_Heading;
+ return N->getKind() == NodeKind::NK_Heading;
}
-};
-/// A fenced code block (``` or ~~~). Lang holds the info string.
-struct FencedCodeNode : BlockNode {
private:
- llvm::StringRef Lang;
- llvm::StringRef Code;
+ unsigned Level;
+ InlineList Children;
+};
+/// A fenced code block (``` or ~~~). Lang holds the info string.
+class FencedCodeNode : public BlockNode {
public:
FencedCodeNode(llvm::StringRef L, llvm::StringRef C)
: BlockNode(NodeKind::NK_FencedCode), Lang(L), Code(C) {}
@@ -173,73 +195,111 @@ struct FencedCodeNode : BlockNode {
llvm::StringRef getCode() const { return Code; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_FencedCode;
+ return N->getKind() == NodeKind::NK_FencedCode;
}
+
+private:
+ llvm::StringRef Lang;
+ llvm::StringRef Code;
};
/// A single item in an unordered or ordered list.
/// ListItemNode is not a BlockNode -- it only lives inside list nodes.
-struct ListItemNode : llvm::ilist_node<ListItemNode> {
- InlineList Children;
+class ListItemNode : public llvm::ilist_node<ListItemNode> {
+public:
ListItemNode() = default;
+ void addChild(InlineNode &N) { Children.push_back(N); }
+ void removeChild(InlineNode &N) { Children.remove(N); }
+ InlineList &children() { return Children; }
+ const InlineList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const;
+ /// Prints to llvm::errs(). Only available in assert builds.
+ /// ListItemNode provides its own dump() since it does not inherit BlockNode.
LLVM_DUMP_METHOD void dump() const;
+
+private:
+ InlineList Children;
};
/// An unordered list (-, *, or + markers).
-struct UnorderedListNode : BlockNode {
- llvm::simple_ilist<ListItemNode> Items;
+class UnorderedListNode : public BlockNode {
+public:
UnorderedListNode() : BlockNode(NodeKind::NK_UnorderedList) {}
+ void addItem(ListItemNode &N) { Items.push_back(N); }
+ void removeItem(ListItemNode &N) { Items.remove(N); }
+ llvm::simple_ilist<ListItemNode> &items() { return Items; }
+ const llvm::simple_ilist<ListItemNode> &items() const { return Items; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_UnorderedList;
+ return N->getKind() == NodeKind::NK_UnorderedList;
}
-};
-/// An ordered list (1. 2. 3. markers). Start holds the first item number.
-struct OrderedListNode : BlockNode {
private:
- unsigned Start;
+ llvm::simple_ilist<ListItemNode> Items;
+};
+/// An ordered list (1. 2. 3. markers). Start holds the first item number.
+class OrderedListNode : public BlockNode {
public:
- llvm::simple_ilist<ListItemNode> Items;
explicit OrderedListNode(unsigned S = 1)
: BlockNode(NodeKind::NK_OrderedList), Start(S) {}
unsigned getStart() const { return Start; }
+ void addItem(ListItemNode &N) { Items.push_back(N); }
+ void removeItem(ListItemNode &N) { Items.remove(N); }
+ llvm::simple_ilist<ListItemNode> &items() { return Items; }
+ const llvm::simple_ilist<ListItemNode> &items() const { return Items; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_OrderedList;
+ return N->getKind() == NodeKind::NK_OrderedList;
}
+
+private:
+ unsigned Start;
+ llvm::simple_ilist<ListItemNode> Items;
};
/// A block quote (> marker).
-struct BlockQuoteNode : BlockNode {
- BlockList Children;
+class BlockQuoteNode : public BlockNode {
+public:
BlockQuoteNode() : BlockNode(NodeKind::NK_BlockQuote) {}
+ void addChild(BlockNode &N) { Children.push_back(N); }
+ void removeChild(BlockNode &N) { Children.remove(N); }
+ BlockList &children() { return Children; }
+ const BlockList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_BlockQuote;
+ return N->getKind() == NodeKind::NK_BlockQuote;
}
+
+private:
+ BlockList Children;
};
/// A thematic break (---, ***, or ___).
-struct ThematicBreakNode : BlockNode {
+class ThematicBreakNode : public BlockNode {
+public:
ThematicBreakNode() : BlockNode(NodeKind::NK_ThematicBreak) {}
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_ThematicBreak;
+ return N->getKind() == NodeKind::NK_ThematicBreak;
}
};
/// The root document node. Contains all top-level block nodes.
-/// Children are added by the parser via push_back on the Children ilist.
-struct DocumentNode : BlockNode {
- BlockList Children;
+class DocumentNode : public BlockNode {
+public:
DocumentNode() : BlockNode(NodeKind::NK_Document) {}
+ void addChild(BlockNode &N) { Children.push_back(N); }
+ void removeChild(BlockNode &N) { Children.remove(N); }
+ BlockList &children() { return Children; }
+ const BlockList &children() const { return Children; }
void print(llvm::raw_ostream &OS) const override;
static bool classof(const BlockNode *N) {
- return N->Kind == NodeKind::NK_Document;
+ return N->getKind() == NodeKind::NK_Document;
}
+
+private:
+ BlockList Children;
};
} // namespace clang::doc::markdown
diff --git a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
index c8871ee4d5e8e..01fbdbbba9bb9 100644
--- a/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp
@@ -14,81 +14,105 @@ using namespace llvm;
namespace {
+/// Returns the text of the first TextNode child in the given inline list.
+static llvm::StringRef firstChildText(const InlineList &L) {
+ return llvm::cast<TextNode>(L.front()).getText();
+}
+
TEST(MarkdownNodeTest, TextNode) {
TextNode N("hello");
- EXPECT_EQ(N.Kind, NodeKind::NK_Text);
+ EXPECT_EQ(N.getKind(), NodeKind::NK_Text);
EXPECT_EQ(N.getText(), "hello");
}
TEST(MarkdownNodeTest, FencedCodeNode) {
FencedCodeNode N("cpp", "int x = 0;\nint y = 1;");
- EXPECT_EQ(N.Kind, NodeKind::NK_FencedCode);
+ EXPECT_EQ(N.getKind(), NodeKind::NK_FencedCode);
EXPECT_EQ(N.getLang(), "cpp");
- EXPECT_TRUE(N.getCode().contains("int x = 0;"));
- EXPECT_TRUE(N.getCode().contains("int y = 1;"));
+ llvm::SmallVector<llvm::StringRef> Lines;
+ N.getCode().split(Lines, '\n');
+ EXPECT_EQ(Lines[0], "int x = 0;");
+ EXPECT_EQ(Lines[1], "int y = 1;");
}
TEST(MarkdownNodeTest, HeadingNode) {
HeadingNode N(2);
- EXPECT_EQ(N.Kind, NodeKind::NK_Heading);
+ EXPECT_EQ(N.getKind(), NodeKind::NK_Heading);
EXPECT_EQ(N.getLevel(), 2u);
}
TEST(MarkdownNodeTest, ThematicBreakNode) {
ThematicBreakNode N;
- EXPECT_EQ(N.Kind, NodeKind::NK_ThematicBreak);
+ EXPECT_EQ(N.getKind(), NodeKind::NK_ThematicBreak);
}
TEST(MarkdownNodeTest, InlineCodeNode) {
InlineCodeNode N("foo()");
- EXPECT_EQ(N.Kind, NodeKind::NK_InlineCode);
+ EXPECT_EQ(N.getKind(), NodeKind::NK_InlineCode);
EXPECT_EQ(N.getCode(), "foo()");
}
TEST(MarkdownNodeTest, EmphasisNode) {
EmphasisNode N;
TextNode Child("emphasized");
- N.Children.push_back(Child);
- EXPECT_EQ(N.Kind, NodeKind::NK_Emphasis);
- EXPECT_FALSE(N.Children.empty());
- EXPECT_EQ(llvm::cast<TextNode>(N.Children.front()).getText(), "emphasized");
+ N.addChild(Child);
+ EXPECT_EQ(N.getKind(), NodeKind::NK_Emphasis);
+ EXPECT_FALSE(N.children().empty());
+ EXPECT_EQ(firstChildText(N.children()), "emphasized");
+}
+
+TEST(MarkdownNodeTest, EmphasisRemoveChild) {
+ EmphasisNode N;
+ TextNode Child("temp");
+ N.addChild(Child);
+ EXPECT_FALSE(N.children().empty());
+ N.removeChild(Child);
+ EXPECT_TRUE(N.children().empty());
}
TEST(MarkdownNodeTest, UnorderedListNode) {
UnorderedListNode N;
- EXPECT_EQ(N.Kind, NodeKind::NK_UnorderedList);
- EXPECT_TRUE(N.Items.empty());
+ EXPECT_EQ(N.getKind(), NodeKind::NK_UnorderedList);
+ EXPECT_TRUE(N.items().empty());
+}
+
+TEST(MarkdownNodeTest, UnorderedListRemoveItem) {
+ UnorderedListNode List;
+ ListItemNode Item;
+ List.addItem(Item);
+ EXPECT_FALSE(List.items().empty());
+ List.removeItem(Item);
+ EXPECT_TRUE(List.items().empty());
}
TEST(MarkdownNodeTest, ParagraphNode) {
ParagraphNode N;
- EXPECT_EQ(N.Kind, NodeKind::NK_Paragraph);
- EXPECT_TRUE(N.Children.empty());
+ EXPECT_EQ(N.getKind(), NodeKind::NK_Paragraph);
+ EXPECT_TRUE(N.children().empty());
}
TEST(MarkdownNodeTest, DocumentNode) {
DocumentNode N;
- EXPECT_EQ(N.Kind, NodeKind::NK_Document);
- EXPECT_TRUE(N.Children.empty());
+ EXPECT_EQ(N.getKind(), NodeKind::NK_Document);
+ EXPECT_TRUE(N.children().empty());
}
TEST(MarkdownNodeTest, ParagraphWithChildren) {
ParagraphNode Para;
TextNode Child("hello");
- Para.Children.push_back(Child);
- EXPECT_FALSE(Para.Children.empty());
- EXPECT_EQ(llvm::cast<TextNode>(Para.Children.front()).getText(), "hello");
+ Para.addChild(Child);
+ EXPECT_FALSE(Para.children().empty());
+ EXPECT_EQ(firstChildText(Para.children()), "hello");
}
TEST(MarkdownNodeTest, UnorderedListWithItems) {
UnorderedListNode List;
ListItemNode Item;
TextNode Child("item text");
- Item.Children.push_back(Child);
- List.Items.push_back(Item);
- EXPECT_FALSE(List.Items.empty());
- EXPECT_EQ(llvm::cast<TextNode>(List.Items.front().Children.front()).getText(),
- "item text");
+ Item.addChild(Child);
+ List.addItem(Item);
+ EXPECT_FALSE(List.items().empty());
+ EXPECT_EQ(firstChildText(List.items().front().children()), "item text");
}
} // namespace
More information about the cfe-commits
mailing list