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 <timcot...@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
+@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
+@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
+@brief brief doc
 
 longer doc
+@param a this is a param
+@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
+@brief brief doc
 
 longer doc
+@param a this is a param
+@param b does not exist
+@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@param bar this is another "
@@ -128,21 +157,46 @@ TEST(SymbolDocumentation, Parse) {
           "",
       },
       {
-          "@brief brief docs\n\n@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
+
+@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) {

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to