[clang-tools-extra] [clangd] extend and rearrange doxygen hover documentation (PR #152918)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 10 07:06:55 PDT 2025
https://github.com/tcottin created https://github.com/llvm/llvm-project/pull/152918
This is the final patch to solve clangd/clangd#529.
This uses the patches from [llvm/llvm-project#140498](https://github.com/llvm/llvm-project/pull/140498) and [llvm/llvm-project#150790](https://github.com/llvm/llvm-project/pull/150790) to improve the hover content for doxygen documented code.
The patch uses the information from the parsed doxygen comment to extend and rearrange the hover documentation:
- own section for brief, notes and warning documentation
- for functions add parameter/template parameter documentation
- for functions add return documentation
Note: this requires to configure
```yaml
Documentation:
CommentFormat: Doxygen
```
in your `.clangd` configuration.
>From bdb6f26c036436c9c964333f7bd43b10181a860e Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Sun, 10 Aug 2025 13:51:24 +0000
Subject: [PATCH] [clangd] rearrange doxygen hover documentation
---
.../clangd/CodeCompletionStrings.cpp | 8 +-
clang-tools-extra/clangd/Hover.cpp | 100 ++++-
clang-tools-extra/clangd/Hover.h | 3 +
.../clangd/SymbolDocumentation.cpp | 103 ++++-
.../clangd/SymbolDocumentation.h | 76 +++-
.../clangd/unittests/HoverTests.cpp | 377 ++++++++++++++++--
.../unittests/SymbolDocumentationTests.cpp | 134 +++++--
7 files changed, 683 insertions(+), 118 deletions(-)
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index d6579640cb0fb..02c3ea8abd8af 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -112,7 +112,7 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
std::string Doc;
if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen &&
- isa<ParmVarDecl>(Decl)) {
+ (isa<ParmVarDecl>(Decl) || isa<TemplateTypeParmDecl>(Decl))) {
// Parameters are documented in their declaration context (function or
// template function).
const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext());
@@ -135,7 +135,11 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
std::string RawDoc;
llvm::raw_string_ostream OS(RawDoc);
- V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS);
+ if (isa<ParmVarDecl>(Decl))
+ V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS);
+ else
+ V.templateTypeParmDocToString(
+ dyn_cast<TemplateTypeParmDecl>(&Decl)->getName(), OS);
Doc = StringRef(RawDoc).trim().str();
} else {
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 0afa90285db52..45e47233c9e1f 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -1276,6 +1276,7 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
HI.Definition =
URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str();
HI.DefinitionLanguage = "";
+ HI.IsIncludeDirective = true;
maybeAddUsedSymbols(AST, HI, Inc);
return HI;
}
@@ -1504,7 +1505,26 @@ markup::Document HoverInfo::presentDoxygen() const {
Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
assert(!Name.empty() && "hover triggered on a nameless symbol");
- Header.appendCode(Name);
+ if (IsIncludeDirective) {
+ Header.appendCode(Name);
+
+ if (!Definition.empty())
+ Output.addParagraph().appendCode(Definition);
+
+ if (!UsedSymbolNames.empty()) {
+ Output.addRuler();
+ usedSymbolNamesToMarkup(Output);
+ }
+
+ return Output;
+ }
+
+ if (!Definition.empty()) {
+ Output.addRuler();
+ definitionScopeToMarkup(Output);
+ } else {
+ Header.appendCode(Name);
+ }
if (!Provider.empty()) {
providerToMarkupParagraph(Output);
@@ -1512,33 +1532,76 @@ markup::Document HoverInfo::presentDoxygen() const {
// Put a linebreak after header to increase readability.
Output.addRuler();
- // Print Types on their own lines to reduce chances of getting line-wrapped by
- // editor, as they might be long.
- if (ReturnType) {
- // For functions we display signature in a list form, e.g.:
- // → `x`
- // Parameters:
- // - `bool param1`
- // - `int param2 = 5`
- Output.addParagraph().appendText("→ ").appendCode(
- llvm::to_string(*ReturnType));
- }
SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts);
+ if (SymbolDoc.hasBriefCommand()) {
+ SymbolDoc.briefToMarkup(Output.addParagraph());
+ Output.addRuler();
+ }
+
+ // For functions we display signature in a list form, e.g.:
+ // Template Parameters:
+ // - `typename T` - description
+ // Parameters:
+ // - `bool param1` - description
+ // - `int param2 = 5` - description
+ // Returns
+ // `type` - description
+ if (TemplateParameters && !TemplateParameters->empty()) {
+ Output.addParagraph().appendBoldText("Template Parameters:");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto &Param : *TemplateParameters) {
+ markup::Paragraph &P = L.addItem().addParagraph();
+ P.appendCode(llvm::to_string(Param));
+ if (SymbolDoc.isTemplateTypeParmDocumented(llvm::to_string(Param.Name))) {
+ P.appendText(" - ");
+ SymbolDoc.templateTypeParmDocToMarkup(llvm::to_string(Param.Name), P);
+ }
+ }
+ Output.addRuler();
+ }
+
if (Parameters && !Parameters->empty()) {
- Output.addParagraph().appendText("Parameters:");
+ Output.addParagraph().appendBoldText("Parameters:");
markup::BulletList &L = Output.addBulletList();
for (const auto &Param : *Parameters) {
markup::Paragraph &P = L.addItem().addParagraph();
P.appendCode(llvm::to_string(Param));
if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) {
- P.appendText(" -");
+ P.appendText(" - ");
SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P);
}
}
+ Output.addRuler();
+ }
+
+ // Print Types on their own lines to reduce chances of getting line-wrapped by
+ // editor, as they might be long.
+ if (ReturnType &&
+ ((ReturnType->Type != "void" && !ReturnType->AKA.has_value()) ||
+ (ReturnType->AKA.has_value() && ReturnType->AKA != "void"))) {
+ Output.addParagraph().appendBoldText("Returns:");
+ markup::Paragraph &P = Output.addParagraph();
+ P.appendCode(llvm::to_string(*ReturnType));
+
+ if (SymbolDoc.hasReturnCommand()) {
+ P.appendText(" - ");
+ SymbolDoc.returnToMarkup(P);
+ }
+ Output.addRuler();
}
+
+ // add specially handled doxygen commands.
+ SymbolDoc.warningsToMarkup(Output);
+ SymbolDoc.notesToMarkup(Output);
+
+ // add any other documentation.
+ SymbolDoc.docToMarkup(Output);
+
+ Output.addRuler();
+
// Don't print Type after Parameters or ReturnType as this will just duplicate
// the information
if (Type && !ReturnType && !Parameters)
@@ -1559,13 +1622,6 @@ markup::Document HoverInfo::presentDoxygen() const {
calleeArgInfoToMarkupParagraph(Output.addParagraph());
}
- SymbolDoc.docToMarkup(Output);
-
- if (!Definition.empty()) {
- Output.addRuler();
- definitionScopeToMarkup(Output);
- }
-
if (!UsedSymbolNames.empty()) {
Output.addRuler();
usedSymbolNamesToMarkup(Output);
@@ -1613,7 +1669,7 @@ markup::Document HoverInfo::presentDefault() const {
}
if (Parameters && !Parameters->empty()) {
- Output.addParagraph().appendText("Parameters: ");
+ Output.addParagraph().appendText("Parameters:");
markup::BulletList &L = Output.addBulletList();
for (const auto &Param : *Parameters)
L.addItem().addParagraph().appendCode(llvm::to_string(Param));
diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index 614180a7b9846..2eb3dd99ab3fc 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -120,6 +120,9 @@ struct HoverInfo {
// alphabetical order.
std::vector<std::string> UsedSymbolNames;
+ /// True if this symbol is a include directive.
+ bool IsIncludeDirective = false;
+
/// Produce a user-readable information based on the specified markup kind.
std::string present(MarkupKind Kind) const;
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp
index dea637b9100da..9152355cd0f79 100644
--- a/clang-tools-extra/clangd/SymbolDocumentation.cpp
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -33,8 +33,8 @@ void commandToMarkup(markup::Paragraph &Out, StringRef Command,
comments::CommandMarkerKind CommandMarker,
StringRef Args) {
Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str());
+ Out.appendSpace();
if (!Args.empty()) {
- Out.appendSpace();
Out.appendEmphasizedText(Args.str());
}
}
@@ -132,7 +132,8 @@ class ParagraphToMarkupDocument
const comments::CommandTraits &Traits;
/// If true, the next leading space after a new line is trimmed.
- bool LastChunkEndsWithNewline = false;
+ /// Initially set it to true, to always trim the first text line.
+ bool LastChunkEndsWithNewline = true;
};
class ParagraphToString
@@ -263,8 +264,76 @@ class BlockCommentToMarkupDocument
StringRef CommentEscapeMarker;
};
-void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName,
- markup::Paragraph &Out) {
+void SymbolDocCommentVisitor::visitBlockCommandComment(
+ const comments::BlockCommandComment *B) {
+ switch (B->getCommandID()) {
+ case comments::CommandTraits::KCI_brief: {
+ if (!BriefParagraph) {
+ BriefParagraph = B->getParagraph();
+ return;
+ }
+ break;
+ }
+ case comments::CommandTraits::KCI_return:
+ case comments::CommandTraits::KCI_returns:
+ if (!ReturnParagraph) {
+ ReturnParagraph = B->getParagraph();
+ return;
+ }
+ break;
+ case comments::CommandTraits::KCI_retval:
+ RetvalParagraphs.push_back(B->getParagraph());
+ return;
+ case comments::CommandTraits::KCI_warning:
+ WarningParagraphs.push_back(B->getParagraph());
+ return;
+ case comments::CommandTraits::KCI_note:
+ NoteParagraphs.push_back(B->getParagraph());
+ return;
+ default:
+ break;
+ }
+
+ // For all other commands, we store them in the UnhandledCommands map.
+ // This allows us to keep the order of the comments.
+ UnhandledCommands[CommentPartIndex] = B;
+ CommentPartIndex++;
+}
+
+void SymbolDocCommentVisitor::paragraphsToMarkup(
+ markup::Document &Out,
+ const llvm::SmallVector<const comments::ParagraphComment *> &Paragraphs)
+ const {
+ if (Paragraphs.empty())
+ return;
+
+ for (const auto *P : Paragraphs) {
+ ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
+ }
+}
+
+void SymbolDocCommentVisitor::briefToMarkup(markup::Paragraph &Out) const {
+ if (!BriefParagraph)
+ return;
+ ParagraphToMarkupDocument(Out, Traits).visit(BriefParagraph);
+}
+
+void SymbolDocCommentVisitor::returnToMarkup(markup::Paragraph &Out) const {
+ if (!ReturnParagraph)
+ return;
+ ParagraphToMarkupDocument(Out, Traits).visit(ReturnParagraph);
+}
+
+void SymbolDocCommentVisitor::notesToMarkup(markup::Document &Out) const {
+ paragraphsToMarkup(Out, NoteParagraphs);
+}
+
+void SymbolDocCommentVisitor::warningsToMarkup(markup::Document &Out) const {
+ paragraphsToMarkup(Out, WarningParagraphs);
+}
+
+void SymbolDocCommentVisitor::parameterDocToMarkup(
+ StringRef ParamName, markup::Paragraph &Out) const {
if (ParamName.empty())
return;
@@ -274,7 +343,7 @@ void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName,
}
void SymbolDocCommentVisitor::parameterDocToString(
- StringRef ParamName, llvm::raw_string_ostream &Out) {
+ StringRef ParamName, llvm::raw_string_ostream &Out) const {
if (ParamName.empty())
return;
@@ -283,9 +352,9 @@ void SymbolDocCommentVisitor::parameterDocToString(
}
}
-void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) {
+void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) const {
for (unsigned I = 0; I < CommentPartIndex; ++I) {
- if (const auto *BC = BlockCommands.lookup(I)) {
+ if (const auto *BC = UnhandledCommands.lookup(I)) {
BlockCommentToMarkupDocument(Out, Traits).visit(BC);
} else if (const auto *P = FreeParagraphs.lookup(I)) {
ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
@@ -293,5 +362,25 @@ void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) {
}
}
+void SymbolDocCommentVisitor::templateTypeParmDocToMarkup(
+ StringRef TemplateParamName, markup::Paragraph &Out) const {
+ if (TemplateParamName.empty())
+ return;
+
+ if (const auto *TP = TemplateParameters.lookup(TemplateParamName)) {
+ ParagraphToMarkupDocument(Out, Traits).visit(TP->getParagraph());
+ }
+}
+
+void SymbolDocCommentVisitor::templateTypeParmDocToString(
+ StringRef TemplateParamName, llvm::raw_string_ostream &Out) const {
+ if (TemplateParamName.empty())
+ return;
+
+ if (const auto *P = TemplateParameters.lookup(TemplateParamName)) {
+ ParagraphToString(Out, Traits).visit(P->getParagraph());
+ }
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h
index b5120ba04e8f1..9d173745965e2 100644
--- a/clang-tools-extra/clangd/SymbolDocumentation.h
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -106,16 +106,43 @@ class SymbolDocCommentVisitor
return Parameters.contains(ParamName);
}
- void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out);
+ bool isTemplateTypeParmDocumented(StringRef ParamName) const {
+ return TemplateParameters.contains(ParamName);
+ }
- void parameterDocToString(StringRef ParamName, llvm::raw_string_ostream &Out);
+ bool hasBriefCommand() const { return BriefParagraph; }
- void docToMarkup(markup::Document &Out);
+ bool hasReturnCommand() const { return ReturnParagraph; }
- void visitBlockCommandComment(const comments::BlockCommandComment *B) {
- BlockCommands[CommentPartIndex] = B;
- CommentPartIndex++;
- }
+ bool hasRetvalCommands() const { return !RetvalParagraphs.empty(); }
+
+ bool hasNoteCommands() const { return !NoteParagraphs.empty(); }
+
+ bool hasWarningCommands() const { return !WarningParagraphs.empty(); }
+
+ /// Converts all unhandled comment commands to a markup document.
+ void docToMarkup(markup::Document &Out) const;
+ /// Converts the "brief" command(s) to a markup document.
+ void briefToMarkup(markup::Paragraph &Out) const;
+ /// Converts the "return" command(s) to a markup document.
+ void returnToMarkup(markup::Paragraph &Out) const;
+ /// Converts the "note" command(s) to a markup document.
+ void notesToMarkup(markup::Document &Out) const;
+ /// Converts the "warning" command(s) to a markup document.
+ void warningsToMarkup(markup::Document &Out) const;
+
+ void visitBlockCommandComment(const comments::BlockCommandComment *B);
+
+ void templateTypeParmDocToMarkup(StringRef TemplateParamName,
+ markup::Paragraph &Out) const;
+
+ void templateTypeParmDocToString(StringRef TemplateParamName,
+ llvm::raw_string_ostream &Out) const;
+
+ void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out) const;
+
+ void parameterDocToString(StringRef ParamName,
+ llvm::raw_string_ostream &Out) const;
void visitParagraphComment(const comments::ParagraphComment *P) {
FreeParagraphs[CommentPartIndex] = P;
@@ -126,6 +153,10 @@ class SymbolDocCommentVisitor
Parameters[P->getParamNameAsWritten()] = P;
}
+ void visitTParamCommandComment(const comments::TParamCommandComment *TP) {
+ TemplateParameters[TP->getParamNameAsWritten()] = std::move(TP);
+ }
+
private:
comments::CommandTraits Traits;
llvm::BumpPtrAllocator Allocator;
@@ -136,17 +167,42 @@ class SymbolDocCommentVisitor
/// This index allows us to keep the order of the other comment parts.
unsigned CommentPartIndex = 0;
+ /// Paragraph of the "brief" command.
+ const comments::ParagraphComment *BriefParagraph = nullptr;
+
+ /// Paragraph of the "return" command.
+ const comments::ParagraphComment *ReturnParagraph = nullptr;
+
+ /// Paragraph(s) of the "note" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> RetvalParagraphs;
+
+ /// Paragraph(s) of the "note" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> NoteParagraphs;
+
+ /// Paragraph(s) of the "warning" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> WarningParagraphs;
+
+ /// All the paragraphs we don't have any special handling for,
+ /// e.g. "details".
+ llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
+ UnhandledCommands;
+
/// Parsed paragaph(s) of the "param" comamnd(s)
llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *>
Parameters;
- /// All the block commands.
- llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
- BlockCommands;
+ /// Parsed paragaph(s) of the "tparam" comamnd(s)
+ llvm::SmallDenseMap<StringRef, const comments::TParamCommandComment *>
+ TemplateParameters;
/// All "free" text paragraphs.
llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *>
FreeParagraphs;
+
+ void
+ paragraphsToMarkup(markup::Document &Out,
+ const llvm::SmallVector<const comments::ParagraphComment *>
+ &Paragraphs) const;
};
} // namespace clangd
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 3331164ab0024..f7e88c66973f3 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -3409,7 +3409,8 @@ TEST(Hover, DocsFromMostSpecial) {
TEST(Hover, Present) {
struct {
const std::function<void(HoverInfo &)> Builder;
- llvm::StringRef ExpectedRender;
+ llvm::StringRef ExpectedMarkdownRender;
+ llvm::StringRef ExpectedDoxygenRender;
} Cases[] = {
{
[](HoverInfo &HI) {
@@ -3417,6 +3418,7 @@ TEST(Hover, Present) {
HI.Name = "X";
},
R"(X)",
+ R"(### `X`)",
},
{
[](HoverInfo &HI) {
@@ -3424,6 +3426,7 @@ TEST(Hover, Present) {
HI.Name = "foo";
},
R"(namespace-alias foo)",
+ R"(### namespace-alias `foo`)",
},
{
[](HoverInfo &HI) {
@@ -3446,6 +3449,24 @@ Size: 10 bytes
documentation
template <typename T, typename C = bool> class Foo {})",
+ R"(### class
+
+---
+```cpp
+template <typename T, typename C = bool> class Foo {}
+```
+
+---
+**Template Parameters:**
+
+- `typename T`
+- `typename C = bool`
+
+---
+documentation
+
+---
+Size: 10 bytes)",
},
{
[](HoverInfo &HI) {
@@ -3476,6 +3497,26 @@ template <typename T, typename C = bool> class Foo {})",
"\n"
"// In namespace ns\n"
"ret_type foo(params) {}",
+ R"(### function
+
+---
+```cpp
+// In namespace ns
+ret_type foo(params) {}
+```
+
+---
+**Parameters:**
+
+-
+- `type (aka can_type)`
+- `type foo (aka can_type)`
+- `type foo = default (aka can_type)`
+
+---
+**Returns:**
+
+`ret_type (aka can_ret_type)`)",
},
{
[](HoverInfo &HI) {
@@ -3502,6 +3543,22 @@ Size: 4 bytes (+4 bytes padding), alignment 4 bytes
// In test::Bar
def)",
+ R"(### field
+
+---
+```cpp
+// In test::Bar
+def
+```
+
+---
+Type: `type (aka can_type)`
+
+Value = `value`
+
+Offset: 12 bytes
+
+Size: 4 bytes (+4 bytes padding), alignment 4 bytes)",
},
{
[](HoverInfo &HI) {
@@ -3528,6 +3585,22 @@ Size: 25 bits (+4 bits padding), alignment 8 bytes
// In test::Bar
def)",
+ R"(### field
+
+---
+```cpp
+// In test::Bar
+def
+```
+
+---
+Type: `type (aka can_type)`
+
+Value = `value`
+
+Offset: 4 bytes and 3 bits
+
+Size: 25 bits (+4 bits padding), alignment 8 bytes)",
},
{
[](HoverInfo &HI) {
@@ -3541,6 +3614,13 @@ def)",
// In test::Bar
public: def)",
+ R"(### field
+
+---
+```cpp
+// In test::Bar
+public: def
+```)",
},
{
[](HoverInfo &HI) {
@@ -3560,6 +3640,18 @@ public: def)",
// In cls<int>
protected: size_t method())",
+ R"(### instance-method
+
+---
+```cpp
+// In cls<int>
+protected: size_t method()
+```
+
+---
+**Returns:**
+
+`size_t (aka unsigned long)`)",
},
{
[](HoverInfo &HI) {
@@ -3587,6 +3679,19 @@ protected: size_t method())",
// In cls
public: cls(int a, int b = 5))",
+ R"(### constructor
+
+---
+```cpp
+// In cls
+public: cls(int a, int b = 5)
+```
+
+---
+**Parameters:**
+
+- `int a`
+- `int b = 5`)",
},
{
[](HoverInfo &HI) {
@@ -3600,6 +3705,13 @@ public: cls(int a, int b = 5))",
// In namespace ns1
private: union foo {})",
+ R"(### union
+
+---
+```cpp
+// In namespace ns1
+private: union foo {}
+```)",
},
{
[](HoverInfo &HI) {
@@ -3625,6 +3737,20 @@ Passed as arg_a
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed as arg_a)",
},
{
[](HoverInfo &HI) {
@@ -3636,6 +3762,10 @@ int foo = 3)",
},
R"(variable foo
+Passed by value)",
+ R"(### variable `foo`
+
+---
Passed by value)",
},
{
@@ -3662,6 +3792,20 @@ Passed by reference as arg_a
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed by reference as arg_a)",
},
{
[](HoverInfo &HI) {
@@ -3687,6 +3831,20 @@ Passed as arg_a (converted to alias_int)
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed as arg_a (converted to alias_int))",
},
{
[](HoverInfo &HI) {
@@ -3702,6 +3860,15 @@ int foo = 3)",
// Expands to
(1 + 1))",
+ R"(### macro
+
+---
+```cpp
+#define PLUS_ONE(X) (X+1)
+
+// Expands to
+(1 + 1)
+```)",
},
{
[](HoverInfo &HI) {
@@ -3727,45 +3894,86 @@ Passed by const reference as arg_a (converted to int)
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed by const reference as arg_a (converted to int))",
},
{
[](HoverInfo &HI) {
HI.Name = "stdio.h";
HI.Definition = "/usr/include/stdio.h";
+ HI.IsIncludeDirective = true;
},
R"(stdio.h
/usr/include/stdio.h)",
+ R"(### `stdio.h`
+
+`/usr/include/stdio.h`)",
},
- {[](HoverInfo &HI) {
- HI.Name = "foo.h";
- HI.UsedSymbolNames = {"Foo", "Bar", "Bar"};
- },
- R"(foo.h
+ {
+ [](HoverInfo &HI) {
+ HI.Name = "foo.h";
+ HI.UsedSymbolNames = {"Foo", "Bar", "Bar"};
+ HI.IsIncludeDirective = true;
+ },
+ R"(foo.h
+
+provides Foo, Bar, Bar)",
+ R"(### `foo.h`
-provides Foo, Bar, Bar)"},
+---
+provides `Foo`, `Bar`, `Bar`)",
+ },
{[](HoverInfo &HI) {
HI.Name = "foo.h";
HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"};
+ HI.IsIncludeDirective = true;
},
R"(foo.h
-provides Foo, Bar, Baz, Foobar, Qux and 1 more)"}};
+provides Foo, Bar, Baz, Foobar, Qux and 1 more)",
+ R"(### `foo.h`
+
+---
+provides `Foo`, `Bar`, `Baz`, `Foobar`, `Qux` and 1 more)"}};
for (const auto &C : Cases) {
HoverInfo HI;
C.Builder(HI);
Config Cfg;
Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown;
WithContextValue WithCfg(Config::Key, std::move(Cfg));
- EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedRender);
+ EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedMarkdownRender);
+ }
+ for (const auto &C : Cases) {
+ HoverInfo HI;
+ C.Builder(HI);
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender);
}
}
TEST(Hover, PresentDocumentation) {
struct {
const std::function<void(HoverInfo &)> Builder;
- llvm::StringRef ExpectedRender;
+ llvm::StringRef ExpectedMarkdownRender;
+ llvm::StringRef ExpectedDoxygenRender;
} Cases[] = {
{[](HoverInfo &HI) {
HI.Kind = index::SymbolKind::Function;
@@ -3777,14 +3985,26 @@ TEST(Hover, PresentDocumentation) {
R"(### function `foo`
---
-**@brief** brief doc
+ at brief brief doc
longer doc
---
```cpp
void foo()
-```)"},
+```)",
+ R"(### function
+
+---
+```cpp
+void foo()
+```
+
+---
+brief doc
+
+---
+longer doc)"},
{[](HoverInfo &HI) {
HI.Kind = index::SymbolKind::Function;
HI.Documentation = "@brief brief doc\n\n"
@@ -3798,14 +4018,31 @@ void foo()
---
→ `int`
-**@brief** brief doc
+ at brief brief doc
longer doc
---
```cpp
int foo()
-```)"},
+```)",
+ R"(### function
+
+---
+```cpp
+int foo()
+```
+
+---
+brief doc
+
+---
+**Returns:**
+
+`int`
+
+---
+longer doc)"},
{[](HoverInfo &HI) {
HI.Kind = index::SymbolKind::Function;
HI.Documentation = "@brief brief doc\n\n"
@@ -3826,18 +4063,40 @@ int foo()
Parameters:
-- `int a` - this is a param
+- `int a`
-**@brief** brief doc
+ at brief brief doc
longer doc
+ at param a this is a param
+ at return it returns something
-**@return** it returns something
+---
+```cpp
+int foo(int a)
+```)",
+ R"(### function
---
```cpp
int foo(int a)
-```)"},
+```
+
+---
+brief doc
+
+---
+**Parameters:**
+
+- `int a` - this is a param
+
+---
+**Returns:**
+
+`int` - it returns something
+
+---
+longer doc)"},
{[](HoverInfo &HI) {
HI.Kind = index::SymbolKind::Function;
HI.Documentation = "@brief brief doc\n\n"
@@ -3858,20 +4117,52 @@ int foo(int a)
Parameters:
-- `int a` - this is a param
+- `int a`
-**@brief** brief doc
+ at brief brief doc
longer doc
+ at param a this is a param
+ at param b does not exist
+ at return it returns something
-**@return** it returns something
+---
+```cpp
+int foo(int a)
+```)",
+ R"(### function
---
```cpp
int foo(int a)
-```)"},
+```
+
+---
+brief doc
+
+---
+**Parameters:**
+
+- `int a` - this is a param
+
+---
+**Returns:**
+
+`int` - it returns something
+
+---
+longer doc)"},
};
+ for (const auto &C : Cases) {
+ HoverInfo HI;
+ C.Builder(HI);
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedMarkdownRender);
+ }
for (const auto &C : Cases) {
HoverInfo HI;
C.Builder(HI);
@@ -3879,7 +4170,7 @@ int foo(int a)
Cfg.Hover.ShowAKA = true;
Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
WithContextValue WithCfg(Config::Key, std::move(Cfg));
- EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedRender);
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender);
}
}
@@ -4057,6 +4348,21 @@ TEST(Hover, PresentRulers) {
"```";
EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedMarkdown);
+ llvm::StringRef ExpectedDoxygenMarkdown = //
+ "### variable\n"
+ "\n"
+ "---\n"
+ "```cpp\n"
+ "def\n"
+ "```\n\n"
+ "---\n"
+ "Value = `val`";
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedDoxygenMarkdown);
+
llvm::StringRef ExpectedPlaintext = R"pt(variable foo
Value = val
@@ -4479,8 +4785,7 @@ TEST(Hover, FunctionParameters) {
HI.Definition = "int a";
HI.Documentation = "";
},
- "### param `a`\n\n---\nType: `int`\n\n---\n```cpp\n// In foo\nint "
- "a\n```"},
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"},
{R"cpp(/// Function doc
/// @param a this is doc for a
void foo(int [[^a]]);
@@ -4494,8 +4799,8 @@ TEST(Hover, FunctionParameters) {
HI.Definition = "int a";
HI.Documentation = "this is doc for a";
},
- "### param `a`\n\n---\nType: `int`\n\nthis is doc for "
- "a\n\n---\n```cpp\n// In foo\nint a\n```"},
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nthis is doc "
+ "for a\n\n---\nType: `int`"},
{R"cpp(/// Function doc
/// @param b this is doc for b
void foo(int [[^a]], int b);
@@ -4509,8 +4814,7 @@ TEST(Hover, FunctionParameters) {
HI.Definition = "int a";
HI.Documentation = "";
},
- "### param `a`\n\n---\nType: `int`\n\n---\n```cpp\n// In foo\nint "
- "a\n```"},
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"},
{R"cpp(/// Function doc
/// @param b this is doc for \p b
void foo(int a, int [[^b]]);
@@ -4524,8 +4828,8 @@ TEST(Hover, FunctionParameters) {
HI.Definition = "int b";
HI.Documentation = "this is doc for \\p b";
},
- "### param `b`\n\n---\nType: `int`\n\nthis is doc for "
- "`b`\n\n---\n```cpp\n// In foo\nint b\n```"},
+ "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is doc "
+ "for `b`\n\n---\nType: `int`"},
{R"cpp(/// Function doc
/// @param b this is doc for \p b
template <typename T>
@@ -4540,8 +4844,8 @@ TEST(Hover, FunctionParameters) {
HI.Definition = "T b";
HI.Documentation = "this is doc for \\p b";
},
- "### param `b`\n\n---\nType: `T`\n\nthis is doc for "
- "`b`\n\n---\n```cpp\n// In foo\nT b\n```"},
+ "### param\n\n---\n```cpp\n// In foo\nT b\n```\n\n---\nthis is doc for "
+ "`b`\n\n---\nType: `T`"},
{R"cpp(/// Function doc
/// @param b this is <b>doc</b> <html-tag attribute/> <another-html-tag attribute="value">for</another-html-tag> \p b
void foo(int a, int [[^b]]);
@@ -4557,10 +4861,9 @@ TEST(Hover, FunctionParameters) {
"this is <b>doc</b> <html-tag attribute/> <another-html-tag "
"attribute=\"value\">for</another-html-tag> \\p b";
},
- "### param `b`\n\n---\nType: `int`\n\nthis is \\<b>doc\\</b> "
- "\\<html-tag attribute/> \\<another-html-tag "
- "attribute=\"value\">for\\</another-html-tag> "
- "`b`\n\n---\n```cpp\n// In foo\nint b\n```"},
+ "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is "
+ "\\<b>doc\\</b> \\<html-tag attribute/> \\<another-html-tag "
+ "attribute=\"value\">for\\</another-html-tag> `b`\n\n---\nType: `int`"},
};
// Create a tiny index, so tests above can verify documentation is fetched.
diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
index 69eb13b2142d2..31a6a149e33c4 100644
--- a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
@@ -15,7 +15,7 @@
namespace clang {
namespace clangd {
-TEST(SymbolDocumentation, Parse) {
+TEST(SymbolDocumentation, UnhandledDocs) {
CommentOptions CommentOpts;
@@ -75,9 +75,9 @@ TEST(SymbolDocumentation, Parse) {
},
{
"\\brief this is a \\n\nbrief description",
- "\\*\\*\\\\brief\\*\\* this is a \nbrief description",
- "**\\brief** this is a \nbrief description",
- "**\\brief** this is a\nbrief description",
+ "",
+ "",
+ "",
},
{
"\\throw exception foo",
@@ -86,18 +86,26 @@ TEST(SymbolDocumentation, Parse) {
"**\\throw** *exception* foo",
},
{
- "\\brief this is a brief description\n\n\\li item 1\n\\li item "
- "2\n\\arg item 3",
- "\\*\\*\\\\brief\\*\\* this is a brief description\n\n- item 1\n\n- "
- "item "
- "2\n\n- "
- "item 3",
- "**\\brief** this is a brief description\n\n- item 1\n\n- item "
- "2\n\n- "
- "item 3",
- "**\\brief** this is a brief description\n\n- item 1\n\n- item "
- "2\n\n- "
- "item 3",
+ R"(\brief this is a brief description
+
+\li item 1
+\li item 2
+\arg item 3)",
+ R"(- item 1
+
+- item 2
+
+- item 3)",
+ R"(- item 1
+
+- item 2
+
+- item 3)",
+ R"(- item 1
+
+- item 2
+
+- item 3)",
},
{
"\\defgroup mygroup this is a group\nthis is not a group description",
@@ -110,15 +118,36 @@ TEST(SymbolDocumentation, Parse) {
"description",
},
{
- "\\verbatim\nthis is a\nverbatim block containing\nsome verbatim "
- "text\n\\endverbatim",
- "\\*\\*@verbatim\\*\\*\n\n```\nthis is a\nverbatim block "
- "containing\nsome "
- "verbatim text\n```\n\n\\*\\*@endverbatim\\*\\*",
- "**@verbatim**\n\n```\nthis is a\nverbatim block containing\nsome "
- "verbatim text\n```\n\n**@endverbatim**",
- "**@verbatim**\n\nthis is a\nverbatim block containing\nsome "
- "verbatim text\n\n**@endverbatim**",
+ R"(\verbatim
+this is a
+verbatim block containing
+some verbatim text
+\endverbatim)",
+ R"(\*\*@verbatim\*\*
+
+```
+this is a
+verbatim block containing
+some verbatim text
+```
+
+\*\*@endverbatim\*\*)",
+ R"(**@verbatim**
+
+```
+this is a
+verbatim block containing
+some verbatim text
+```
+
+**@endverbatim**)",
+ R"(**@verbatim**
+
+this is a
+verbatim block containing
+some verbatim text
+
+**@endverbatim**)",
},
{
"@param foo this is a parameter\n at param bar this is another "
@@ -128,21 +157,46 @@ TEST(SymbolDocumentation, Parse) {
"",
},
{
- "@brief brief docs\n\n at param foo this is a parameter\n\nMore "
- "description\ndocumentation",
- "\\*\\*@brief\\*\\* brief docs\n\nMore description\ndocumentation",
- "**@brief** brief docs\n\nMore description\ndocumentation",
- "**@brief** brief docs\n\nMore description documentation",
- },
- {
- "<b>this is a bold text</b>\nnormal text\n<i>this is an italic "
- "text</i>\n<code>this is a code block</code>",
- "\\<b>this is a bold text\\</b>\nnormal text\n\\<i>this is an italic "
- "text\\</i>\n\\<code>this is a code block\\</code>",
- "\\<b>this is a bold text\\</b>\nnormal text\n\\<i>this is an italic "
- "text\\</i>\n\\<code>this is a code block\\</code>",
- "<b>this is a bold text</b> normal text <i>this is an italic "
- "text</i> <code>this is a code block</code>",
+ R"(@brief brief docs
+
+ at param foo this is a parameter
+
+\brief another brief?
+
+\details these are details
+
+More description
+documentation)",
+ R"(\*\*\\brief\*\* another brief?
+
+\*\*\\details\*\* these are details
+
+More description
+documentation)",
+ R"(**\brief** another brief?
+
+**\details** these are details
+
+More description
+documentation)",
+ R"(**\brief** another brief?
+
+**\details** these are details
+
+More description documentation)",
+ },
+ {
+ R"(<b>this is a bold text</b>
+normal text<i>this is an italic text</i>
+<code>this is a code block</code>)",
+ R"(\<b>this is a bold text\</b>
+normal text\<i>this is an italic text\</i>
+\<code>this is a code block\</code>)",
+ R"(\<b>this is a bold text\</b>
+normal text\<i>this is an italic text\</i>
+\<code>this is a code block\</code>)",
+ "<b>this is a bold text</b> normal text<i>this is an italic text</i> "
+ "<code>this is a code block</code>",
},
};
for (const auto &C : Cases) {
More information about the cfe-commits
mailing list