Author: Neil Nair Date: 2026-07-03T10:51:15-07:00 New Revision: b6976d223eeb40091685e3b20e9c41f42f6bd6e0
URL: https://github.com/llvm/llvm-project/commit/b6976d223eeb40091685e3b20e9c41f42f6bd6e0 DIFF: https://github.com/llvm/llvm-project/commit/b6976d223eeb40091685e3b20e9c41f42f6bd6e0.diff LOG: [clang-doc] Add Markdown AST node type definitions (#205609) Markdown AST node hierarchy for clang-doc/support. Separate Block and Inline node hierarchies using llvm::ilist_node and llvm::simple_ilist. Virtual print() in base classes with LLVM_DUMP_METHOD dump() inherited by subclasses. clangDocMarkdown is its own CMake target. Unit tests exercise node construction, accessors, and children. Assisted-by: Claude Added: clang-tools-extra/clang-doc/support/Markdown.cpp clang-tools-extra/clang-doc/support/Markdown.h clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp Modified: clang-tools-extra/clang-doc/support/CMakeLists.txt clang-tools-extra/unittests/clang-doc/CMakeLists.txt Removed: ################################################################################ diff --git a/clang-tools-extra/clang-doc/support/CMakeLists.txt b/clang-tools-extra/clang-doc/support/CMakeLists.txt index 8ac913ffbe998..2ea459b8a9404 100644 --- a/clang-tools-extra/clang-doc/support/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/support/CMakeLists.txt @@ -5,6 +5,13 @@ set(LLVM_LINK_COMPONENTS ) add_clang_library(clangDocSupport STATIC + PARTIAL_SOURCES_INTENDED File.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 new file mode 100644 index 0000000000000..e7049822543c1 --- /dev/null +++ b/clang-tools-extra/clang-doc/support/Markdown.cpp @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// 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" + +namespace clang::doc::markdown { + +// TODO: print() currently outputs nodes flat with no indentation. Add +// S-expression style formatting (as used by the Swift AST printer) to make +// dumped trees easier to read. + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) +LLVM_DUMP_METHOD void InlineNode::dump() const { print(llvm::errs()); } +#endif + +void TextNode::print(llvm::raw_ostream &OS) const { + OS << "TextNode: " << getText() << "\n"; +} + +void InlineCodeNode::print(llvm::raw_ostream &OS) const { + OS << "InlineCodeNode: " << getCode() << "\n"; +} + +void EmphasisNode::print(llvm::raw_ostream &OS) const { + OS << "EmphasisNode\n"; + for (const auto &Child : children()) + Child.print(OS); +} + +void StrongNode::print(llvm::raw_ostream &OS) const { + OS << "StrongNode\n"; + for (const auto &Child : children()) + Child.print(OS); +} + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) +LLVM_DUMP_METHOD void BlockNode::dump() const { print(llvm::errs()); } +#endif + +void ParagraphNode::print(llvm::raw_ostream &OS) const { + OS << "ParagraphNode\n"; + 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()) + Child.print(OS); +} + +void FencedCodeNode::print(llvm::raw_ostream &OS) const { + OS << "FencedCodeNode: lang=" << getLang() << "\n" << getCode() << "\n"; +} + +void ListItemNode::print(llvm::raw_ostream &OS) const { + OS << "ListItemNode\n"; + for (const auto &Child : children()) + Child.print(OS); +} + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) +LLVM_DUMP_METHOD void ListItemNode::dump() const { print(llvm::errs()); } +#endif + +void UnorderedListNode::print(llvm::raw_ostream &OS) const { + OS << "UnorderedListNode\n"; + 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()) + Item.print(OS); +} + +void BlockQuoteNode::print(llvm::raw_ostream &OS) const { + OS << "BlockQuoteNode\n"; + for (const auto &Child : children()) + Child.print(OS); +} + +void ThematicBreakNode::print(llvm::raw_ostream &OS) const { + OS << "ThematicBreakNode\n"; +} + +void DocumentNode::print(llvm::raw_ostream &OS) const { + OS << "DocumentNode\n"; + for (const auto &Child : children()) + Child.print(OS); +} + +} // namespace clang::doc::markdown 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..f92624bac7c86 --- /dev/null +++ b/clang-tools-extra/clang-doc/support/Markdown.h @@ -0,0 +1,315 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \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. +/// +//===----------------------------------------------------------------------===// + +#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/Casting.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/raw_ostream.h" + +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, + NK_Table, // TODO: add TableNode + NK_UnorderedList, + NK_OrderedList, + NK_BlockQuote, + NK_ThematicBreak, + NK_Document, +}; + +/// Base class for all inline nodes. Inline nodes represent span-level content +/// 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; + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) + /// Prints to llvm::errs(). Only available in assert builds. + LLVM_DUMP_METHOD void dump() const; +#endif + +private: + NodeKind Kind; +}; + +using InlineList = llvm::simple_ilist<InlineNode>; + +/// A plain text run. +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->getKind() == NodeKind::NK_Text; + } + +private: + 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->getKind() == NodeKind::NK_InlineCode; + } + +private: + llvm::StringRef Code; +}; + +/// An emphasis span (* or _). +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->getKind() == NodeKind::NK_Emphasis; + } + +private: + InlineList Children; +}; + +/// A strong emphasis span (** or __). +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->getKind() == NodeKind::NK_Strong; + } + +private: + InlineList Children; +}; + +/// Base class for all block nodes. Block nodes represent structural constructs +/// 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; + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) + /// Prints to llvm::errs(). Only available in assert builds. + LLVM_DUMP_METHOD void dump() const; +#endif + +private: + NodeKind Kind; +}; + +using BlockList = llvm::simple_ilist<BlockNode>; + +/// A paragraph of inline content. +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->getKind() == NodeKind::NK_Paragraph; + } + +private: + InlineList Children; +}; + +/// An ATX heading (# through ######). +class HeadingNode : public BlockNode { +public: + 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->getKind() == NodeKind::NK_Heading; + } + +private: + 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) {} + llvm::StringRef getLang() const { return Lang; } + llvm::StringRef getCode() const { return Code; } + void print(llvm::raw_ostream &OS) const override; + static bool classof(const BlockNode *N) { + 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. +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; + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) + /// 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; +#endif + +private: + InlineList Children; +}; + +using ItemList = llvm::simple_ilist<ListItemNode>; + +/// An unordered list (-, *, or + markers). +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); } + ItemList &items() { return Items; } + const ItemList &items() const { return Items; } + void print(llvm::raw_ostream &OS) const override; + static bool classof(const BlockNode *N) { + return N->getKind() == NodeKind::NK_UnorderedList; + } + +private: + ItemList Items; +}; + +/// An ordered list (1. 2. 3. markers). Start holds the first item number. +class OrderedListNode : public BlockNode { +public: + 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); } + ItemList &items() { return Items; } + const ItemList &items() const { return Items; } + void print(llvm::raw_ostream &OS) const override; + static bool classof(const BlockNode *N) { + return N->getKind() == NodeKind::NK_OrderedList; + } + +private: + unsigned Start; + ItemList Items; +}; + +/// A block quote (> marker). +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->getKind() == NodeKind::NK_BlockQuote; + } + +private: + BlockList Children; +}; + +/// A thematic break (---, ***, or ___). +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->getKind() == NodeKind::NK_ThematicBreak; + } +}; + +/// The root document node. Contains all top-level block nodes. +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->getKind() == NodeKind::NK_Document; + } + +private: + BlockList Children; +}; + +} // 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 01b34ec9a791e..97a26f0e24d09 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 + clangDocMarkdown 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..01fbdbbba9bb9 --- /dev/null +++ b/clang-tools-extra/unittests/clang-doc/MarkdownParserTest.cpp @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// 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 "gtest/gtest.h" + +using namespace clang::doc::markdown; +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.getKind(), NodeKind::NK_Text); + EXPECT_EQ(N.getText(), "hello"); +} + +TEST(MarkdownNodeTest, FencedCodeNode) { + FencedCodeNode N("cpp", "int x = 0;\nint y = 1;"); + EXPECT_EQ(N.getKind(), NodeKind::NK_FencedCode); + EXPECT_EQ(N.getLang(), "cpp"); + 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.getKind(), NodeKind::NK_Heading); + EXPECT_EQ(N.getLevel(), 2u); +} + +TEST(MarkdownNodeTest, ThematicBreakNode) { + ThematicBreakNode N; + EXPECT_EQ(N.getKind(), NodeKind::NK_ThematicBreak); +} + +TEST(MarkdownNodeTest, InlineCodeNode) { + InlineCodeNode N("foo()"); + EXPECT_EQ(N.getKind(), NodeKind::NK_InlineCode); + EXPECT_EQ(N.getCode(), "foo()"); +} + +TEST(MarkdownNodeTest, EmphasisNode) { + EmphasisNode N; + TextNode Child("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.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.getKind(), NodeKind::NK_Paragraph); + EXPECT_TRUE(N.children().empty()); +} + +TEST(MarkdownNodeTest, DocumentNode) { + DocumentNode N; + EXPECT_EQ(N.getKind(), NodeKind::NK_Document); + EXPECT_TRUE(N.children().empty()); +} + +TEST(MarkdownNodeTest, ParagraphWithChildren) { + ParagraphNode Para; + TextNode Child("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.addChild(Child); + List.addItem(Item); + EXPECT_FALSE(List.items().empty()); + EXPECT_EQ(firstChildText(List.items().front().children()), "item text"); +} + +} // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
