[clang] [clang-tools-extra] [llvm] [clangd][WIP] Add doxygen parsing using standalone doxygen parser (PR #128591)

via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 24 14:52:24 PST 2025


https://github.com/tcottin created https://github.com/llvm/llvm-project/pull/128591

Continue the work on #78491 and  #127451 to fix clangd/clangd#529.

In #78491 the work to use the clang doxygen parser without having the ASTContext available was started.
This allows to parse doxygen comments from raw strings which are already available in the clangd index.

In #127451 the doxygen parsing was implemented during the creation of the index.
The final parsed documentation was saved in the index and the rendering of the documentation for hovering was implemented.

This PR combines #78491 and #127451 by finalizing the implementation of the standalone doxygen parser and using the doxygen parser to render the documentation on hover requests.

>From 3cd9527c14d4668dcc44b84301ae25b132a3d825 Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Mon, 24 Feb 2025 22:23:06 +0000
Subject: [PATCH] [clangd][WIP] Add doxygen parsing using standalone doxygen
 parser

---
 clang-tools-extra/clangd/CMakeLists.txt       |   1 +
 .../clangd/CodeCompletionStrings.cpp          |  45 ++
 .../clangd/CodeCompletionStrings.h            |  23 +
 clang-tools-extra/clangd/Hover.cpp            | 135 ++----
 clang-tools-extra/clangd/Hover.h              |  54 +--
 .../clangd/SymbolDocumentation.cpp            | 415 ++++++++++++++++++
 .../clangd/SymbolDocumentation.h              |  88 ++++
 clang-tools-extra/clangd/support/Markup.cpp   |  14 +
 clang-tools-extra/clangd/support/Markup.h     |   6 +
 .../unittests/CodeCompletionStringsTests.cpp  | 219 +++++++++
 .../clangd/unittests/HoverTests.cpp           |  29 +-
 .../clangd/unittests/support/MarkupTests.cpp  |  30 ++
 clang/include/clang/AST/Comment.h             |  22 +
 clang/include/clang/AST/CommentSema.h         |   1 +
 clang/lib/AST/CommentParser.cpp               |   5 +-
 clang/lib/AST/CommentSema.cpp                 |   3 +-
 .../clang-tools-extra/clangd/BUILD.gn         |   1 +
 17 files changed, 942 insertions(+), 149 deletions(-)
 create mode 100644 clang-tools-extra/clangd/SymbolDocumentation.cpp
 create mode 100644 clang-tools-extra/clangd/SymbolDocumentation.h

diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 6f10afe4a5625..2fda21510f046 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC
   SemanticHighlighting.cpp
   SemanticSelection.cpp
   SourceCode.cpp
+  SymbolDocumentation.cpp
   SystemIncludeExtractor.cpp
   TidyProvider.cpp
   TUScheduler.cpp
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index 9b4442b0bb76f..6a6377c6c34a3 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -9,6 +9,10 @@
 #include "CodeCompletionStrings.h"
 #include "clang-c/Index.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentLexer.h"
+#include "clang/AST/CommentParser.h"
+#include "clang/AST/CommentSema.h"
 #include "clang/AST/RawCommentList.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
@@ -316,5 +320,46 @@ std::string getReturnType(const CodeCompletionString &CCS) {
   return "";
 }
 
+void docCommentToMarkup(
+    markup::Document &Doc, llvm::StringRef Comment,
+    llvm::BumpPtrAllocator &Allocator, comments::CommandTraits &Traits,
+    std::optional<SymbolPrintedType> SymbolType,
+    std::optional<SymbolPrintedType> SymbolReturnType,
+    const std::optional<std::vector<SymbolParam>> &SymbolParameters) {
+
+  // The comment lexer expects doxygen markers, so add them back.
+  // We need to use the /// style doxygen markers because the comment could
+  // contain the closing the closing tag "*/" of a C Style "/** */" comment
+  // which would break the parsing if we would just enclose the comment text
+  // with "/** */".
+  std::string CommentWithMarkers = "///";
+  for (char C : Comment) {
+    if (C == '\n') {
+      CommentWithMarkers += "\n///";
+    } else {
+      CommentWithMarkers += C;
+    }
+  }
+  SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers);
+
+  SourceManager &SourceMgr = SourceMgrForFile.get();
+  // The doxygen Sema requires a Diagostics consumer, since it reports warnings
+  // e.g. when parameters are not documented correctly.
+  // These warnings are not relevant for us, so we can ignore them.
+  SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer);
+
+  comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits,
+                   /*PP=*/nullptr);
+  comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits,
+                    SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()),
+                    CommentWithMarkers.data(),
+                    CommentWithMarkers.data() + CommentWithMarkers.size());
+  comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(),
+                     Traits);
+
+  fullCommentToMarkupDocument(Doc, P.parseFullComment(), Traits, SymbolType,
+                              SymbolReturnType, SymbolParameters);
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.h b/clang-tools-extra/clangd/CodeCompletionStrings.h
index fa81ad64d406c..b440849f0eb49 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.h
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.h
@@ -14,11 +14,17 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
 
+#include "SymbolDocumentation.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
 
 namespace clang {
 class ASTContext;
 
+namespace comments {
+class CommandTraits;
+class FullComment;
+} // namespace comments
+
 namespace clangd {
 
 /// Gets a minimally formatted documentation comment of \p Result, with comment
@@ -67,6 +73,23 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
 /// is usually the return type of a function.
 std::string getReturnType(const CodeCompletionString &CCS);
 
+/// \brief Parse the \p Comment as doxygen comment and save the result in the
+/// given markup Document \p Doc.
+///
+/// It is assumed that comment markers have already been stripped (e.g. via
+/// getDocComment()).
+///
+/// This uses the Clang doxygen comment parser to parse the comment, and then
+/// converts the parsed comment to a markup::Document. The resulting document is
+/// a combination of the symbol information \p SymbolType, \p SymbolReturnType,
+/// and \p SymbolParameters and the parsed doxygen comment.
+void docCommentToMarkup(
+    markup::Document &Doc, llvm::StringRef Comment,
+    llvm::BumpPtrAllocator &Allocator, comments::CommandTraits &Traits,
+    std::optional<SymbolPrintedType> SymbolType,
+    std::optional<SymbolPrintedType> SymbolReturnType,
+    const std::optional<std::vector<SymbolParam>> &SymbolParameters);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 3ab3d89030520..b488011f8abfc 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -17,6 +17,7 @@
 #include "ParsedAST.h"
 #include "Selection.h"
 #include "SourceCode.h"
+#include "SymbolDocumentation.h"
 #include "clang-include-cleaner/Analysis.h"
 #include "clang-include-cleaner/IncludeSpeller.h"
 #include "clang-include-cleaner/Types.h"
@@ -40,6 +41,7 @@
 #include "clang/AST/Type.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/Specifiers.h"
@@ -160,14 +162,14 @@ const char *getMarkdownLanguage(const ASTContext &Ctx) {
   return LangOpts.ObjC ? "objective-c" : "cpp";
 }
 
-HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
+SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
                                  const PrintingPolicy &PP) {
   // TypePrinter doesn't resolve decltypes, so resolve them here.
   // FIXME: This doesn't handle composite types that contain a decltype in them.
   // We should rather have a printing policy for that.
   while (!QT.isNull() && QT->isDecltypeType())
     QT = QT->castAs<DecltypeType>()->getUnderlyingType();
-  HoverInfo::PrintedType Result;
+  SymbolPrintedType Result;
   llvm::raw_string_ostream OS(Result.Type);
   // Special case: if the outer type is a tag type without qualifiers, then
   // include the tag for extra clarity.
@@ -189,15 +191,15 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
   return Result;
 }
 
-HoverInfo::PrintedType printType(const TemplateTypeParmDecl *TTP) {
-  HoverInfo::PrintedType Result;
+SymbolPrintedType printType(const TemplateTypeParmDecl *TTP) {
+  SymbolPrintedType Result;
   Result.Type = TTP->wasDeclaredWithTypename() ? "typename" : "class";
   if (TTP->isParameterPack())
     Result.Type += "...";
   return Result;
 }
 
-HoverInfo::PrintedType printType(const NonTypeTemplateParmDecl *NTTP,
+SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
                                  const PrintingPolicy &PP) {
   auto PrintedType = printType(NTTP->getType(), NTTP->getASTContext(), PP);
   if (NTTP->isParameterPack()) {
@@ -208,9 +210,9 @@ HoverInfo::PrintedType printType(const NonTypeTemplateParmDecl *NTTP,
   return PrintedType;
 }
 
-HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
+SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
                                  const PrintingPolicy &PP) {
-  HoverInfo::PrintedType Result;
+  SymbolPrintedType Result;
   llvm::raw_string_ostream OS(Result.Type);
   OS << "template <";
   llvm::StringRef Sep = "";
@@ -230,14 +232,14 @@ HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
   return Result;
 }
 
-std::vector<HoverInfo::Param>
+std::vector<SymbolParam>
 fetchTemplateParameters(const TemplateParameterList *Params,
                         const PrintingPolicy &PP) {
   assert(Params);
-  std::vector<HoverInfo::Param> TempParameters;
+  std::vector<SymbolParam> TempParameters;
 
   for (const Decl *Param : *Params) {
-    HoverInfo::Param P;
+    SymbolParam P;
     if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) {
       P.Type = printType(TTP);
 
@@ -351,41 +353,13 @@ void enhanceFromIndex(HoverInfo &Hover, const NamedDecl &ND,
   });
 }
 
-// Default argument might exist but be unavailable, in the case of unparsed
-// arguments for example. This function returns the default argument if it is
-// available.
-const Expr *getDefaultArg(const ParmVarDecl *PVD) {
-  // Default argument can be unparsed or uninstantiated. For the former we
-  // can't do much, as token information is only stored in Sema and not
-  // attached to the AST node. For the latter though, it is safe to proceed as
-  // the expression is still valid.
-  if (!PVD->hasDefaultArg() || PVD->hasUnparsedDefaultArg())
-    return nullptr;
-  return PVD->hasUninstantiatedDefaultArg() ? PVD->getUninstantiatedDefaultArg()
-                                            : PVD->getDefaultArg();
-}
-
-HoverInfo::Param toHoverInfoParam(const ParmVarDecl *PVD,
-                                  const PrintingPolicy &PP) {
-  HoverInfo::Param Out;
-  Out.Type = printType(PVD->getType(), PVD->getASTContext(), PP);
-  if (!PVD->getName().empty())
-    Out.Name = PVD->getNameAsString();
-  if (const Expr *DefArg = getDefaultArg(PVD)) {
-    Out.Default.emplace();
-    llvm::raw_string_ostream OS(*Out.Default);
-    DefArg->printPretty(OS, nullptr, PP);
-  }
-  return Out;
-}
-
 // Populates Type, ReturnType, and Parameters for function-like decls.
 void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D,
                                const FunctionDecl *FD,
                                const PrintingPolicy &PP) {
   HI.Parameters.emplace();
   for (const ParmVarDecl *PVD : FD->parameters())
-    HI.Parameters->emplace_back(toHoverInfoParam(PVD, PP));
+    HI.Parameters->emplace_back(createSymbolParam(PVD, PP));
 
   // We don't want any type info, if name already contains it. This is true for
   // constructors/destructors and conversion operators.
@@ -626,6 +600,9 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP,
   HI.Name = printName(Ctx, *D);
   const auto *CommentD = getDeclForComment(D);
   HI.Documentation = getDeclComment(Ctx, *CommentD);
+  // safe the language options to be able to create the comment::CommandTraits
+  // to parse the documentation
+  HI.CommentOpts = D->getASTContext().getLangOpts().CommentOpts;
   enhanceFromIndex(HI, *CommentD, Index);
   if (HI.Documentation.empty())
     HI.Documentation = synthesizeDocumentation(D);
@@ -812,7 +789,7 @@ HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok,
   return HI;
 }
 
-std::string typeAsDefinition(const HoverInfo::PrintedType &PType) {
+std::string typeAsDefinition(const SymbolPrintedType &PType) {
   std::string Result;
   llvm::raw_string_ostream OS(Result);
   OS << PType.Type;
@@ -1095,7 +1072,7 @@ void maybeAddCalleeArgInfo(const SelectionTree::Node *N, HoverInfo &HI,
 
     // Extract matching argument from function declaration.
     if (const ParmVarDecl *PVD = Parameters[I]) {
-      HI.CalleeArgInfo.emplace(toHoverInfoParam(PVD, PP));
+      HI.CalleeArgInfo.emplace(createSymbolParam(PVD, PP));
       if (N == &OuterNode)
         PassType.PassBy = getPassMode(PVD->getType());
     }
@@ -1455,30 +1432,38 @@ markup::Document HoverInfo::present() 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));
-  }
 
-  if (Parameters && !Parameters->empty()) {
-    Output.addParagraph().appendText("Parameters: ");
-    markup::BulletList &L = Output.addBulletList();
-    for (const auto &Param : *Parameters)
-      L.addItem().addParagraph().appendCode(llvm::to_string(Param));
-  }
+  if (!Documentation.empty()) {
+    llvm::BumpPtrAllocator Allocator;
+    comments::CommandTraits Traits(Allocator, CommentOpts);
+    docCommentToMarkup(Output, Documentation, Allocator, Traits, Type, ReturnType,
+                 Parameters);
+  } else {
+    // 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));
+    }
 
-  // Don't print Type after Parameters or ReturnType as this will just duplicate
-  // the information
-  if (Type && !ReturnType && !Parameters)
-    Output.addParagraph().appendText("Type: ").appendCode(
-        llvm::to_string(*Type));
+    if (Parameters && !Parameters->empty()) {
+      Output.addParagraph().appendText("Parameters: ");
+      markup::BulletList &L = Output.addBulletList();
+      for (const auto &Param : *Parameters)
+        L.addItem().addParagraph().appendCode(llvm::to_string(Param));
+    }
+
+    // Don't print Type after Parameters or ReturnType as this will just duplicate
+    // the information
+    if (Type && !ReturnType && !Parameters)
+      Output.addParagraph().appendText("Type: ").appendCode(
+          llvm::to_string(*Type));
+  }
 
   if (Value) {
     markup::Paragraph &P = Output.addParagraph();
@@ -1518,9 +1503,6 @@ markup::Document HoverInfo::present() const {
     Output.addParagraph().appendText(OS.str());
   }
 
-  if (!Documentation.empty())
-    parseDocumentation(Documentation, Output);
-
   if (!Definition.empty()) {
     Output.addRuler();
     std::string Buffer;
@@ -1646,26 +1628,5 @@ void parseDocumentation(llvm::StringRef Input, markup::Document &Output) {
   FlushParagraph();
 }
 
-llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
-                              const HoverInfo::PrintedType &T) {
-  OS << T.Type;
-  if (T.AKA)
-    OS << " (aka " << *T.AKA << ")";
-  return OS;
-}
-
-llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
-                              const HoverInfo::Param &P) {
-  if (P.Type)
-    OS << P.Type->Type;
-  if (P.Name)
-    OS << " " << *P.Name;
-  if (P.Default)
-    OS << " = " << *P.Default;
-  if (P.Type && P.Type->AKA)
-    OS << " (aka " << *P.Type->AKA << ")";
-  return OS;
-}
-
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index fe689de44732e..f485f02f3d516 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -11,6 +11,7 @@
 
 #include "ParsedAST.h"
 #include "Protocol.h"
+#include "SymbolDocumentation.h"
 #include "support/Markup.h"
 #include "clang/Index/IndexSymbol.h"
 #include <optional>
@@ -25,33 +26,6 @@ namespace clangd {
 /// embedding clients can use the structured information to provide their own
 /// UI.
 struct HoverInfo {
-  /// Contains pretty-printed type and desugared type
-  struct PrintedType {
-    PrintedType() = default;
-    PrintedType(const char *Type) : Type(Type) {}
-    PrintedType(const char *Type, const char *AKAType)
-        : Type(Type), AKA(AKAType) {}
-
-    /// Pretty-printed type
-    std::string Type;
-    /// Desugared type
-    std::optional<std::string> AKA;
-  };
-
-  /// Represents parameters of a function, a template or a macro.
-  /// For example:
-  /// - void foo(ParamType Name = DefaultValue)
-  /// - #define FOO(Name)
-  /// - template <ParamType Name = DefaultType> class Foo {};
-  struct Param {
-    /// The printable parameter type, e.g. "int", or "typename" (in
-    /// TemplateParameters), might be std::nullopt for macro parameters.
-    std::optional<PrintedType> Type;
-    /// std::nullopt for unnamed parameters.
-    std::optional<std::string> Name;
-    /// std::nullopt if no default is provided.
-    std::optional<std::string> Default;
-  };
 
   /// For a variable named Bar, declared in clang::clangd::Foo::getFoo the
   /// following fields will hold:
@@ -74,6 +48,8 @@ struct HoverInfo {
   std::optional<Range> SymRange;
   index::SymbolKind Kind = index::SymbolKind::Unknown;
   std::string Documentation;
+  // required to create a comments::CommandTraits object without the ASTContext
+  CommentOptions CommentOpts;
   /// Source code containing the definition of the symbol.
   std::string Definition;
   const char *DefinitionLanguage = "cpp";
@@ -82,13 +58,13 @@ struct HoverInfo {
   std::string AccessSpecifier;
   /// Printable variable type.
   /// Set only for variables.
-  std::optional<PrintedType> Type;
+  std::optional<SymbolPrintedType> Type;
   /// Set for functions and lambdas.
-  std::optional<PrintedType> ReturnType;
+  std::optional<SymbolPrintedType> ReturnType;
   /// Set for functions, lambdas and macros with parameters.
-  std::optional<std::vector<Param>> Parameters;
+  std::optional<std::vector<SymbolParam>> Parameters;
   /// Set for all templates(function, class, variable).
-  std::optional<std::vector<Param>> TemplateParameters;
+  std::optional<std::vector<SymbolParam>> TemplateParameters;
   /// Contains the evaluated value of the symbol if available.
   std::optional<std::string> Value;
   /// Contains the bit-size of fields and types where it's interesting.
@@ -101,7 +77,7 @@ struct HoverInfo {
   std::optional<uint64_t> Align;
   // Set when symbol is inside function call. Contains information extracted
   // from the callee definition about the argument this is passed as.
-  std::optional<Param> CalleeArgInfo;
+  std::optional<SymbolParam> CalleeArgInfo;
   struct PassType {
     // How the variable is passed to callee.
     enum PassMode { Ref, ConstRef, Value };
@@ -122,11 +98,6 @@ struct HoverInfo {
   markup::Document present() const;
 };
 
-inline bool operator==(const HoverInfo::PrintedType &LHS,
-                       const HoverInfo::PrintedType &RHS) {
-  return std::tie(LHS.Type, LHS.AKA) == std::tie(RHS.Type, RHS.AKA);
-}
-
 inline bool operator==(const HoverInfo::PassType &LHS,
                        const HoverInfo::PassType &RHS) {
   return std::tie(LHS.PassBy, LHS.Converted) ==
@@ -137,15 +108,6 @@ inline bool operator==(const HoverInfo::PassType &LHS,
 // FIXME: move to another file so CodeComplete doesn't depend on Hover.
 void parseDocumentation(llvm::StringRef Input, markup::Document &Output);
 
-llvm::raw_ostream &operator<<(llvm::raw_ostream &,
-                              const HoverInfo::PrintedType &);
-llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
-inline bool operator==(const HoverInfo::Param &LHS,
-                       const HoverInfo::Param &RHS) {
-  return std::tie(LHS.Type, LHS.Name, LHS.Default) ==
-         std::tie(RHS.Type, RHS.Name, RHS.Default);
-}
-
 /// Get the hover information when hovering at \p Pos.
 std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
                                   const format::FormatStyle &Style,
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp
new file mode 100644
index 0000000000000..003110a05f524
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -0,0 +1,415 @@
+//===--- SymbolDocumentation.cpp ==-------------------------------*- C++-*-===//
+//
+// 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 "SymbolDocumentation.h"
+#include "Config.h"
+#include "support/Markup.h"
+#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentVisitor.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ScopedPrinter.h"
+
+namespace clang {
+namespace clangd {
+
+void commandToMarkup(markup::Paragraph &Out, StringRef Command,
+                     comments::CommandMarkerKind CommandMarker,
+                     StringRef Args) {
+  Out.appendBoldText(
+      (CommandMarker == (comments::CommandMarkerKind::CMK_At) ? "@" : "\\") +
+      Command.str());
+  if (!Args.empty()) {
+    Out.appendSpace();
+    Out.appendEmphasizedText(Args.str());
+  }
+}
+
+class ParagraphToMarkupDocument
+    : public comments::ConstCommentVisitor<ParagraphToMarkupDocument> {
+public:
+  ParagraphToMarkupDocument(markup::Paragraph &Out,
+                            const comments::CommandTraits &Traits)
+      : Out(Out), Traits(Traits) {}
+
+  void visitParagraphComment(const comments::ParagraphComment *C) {
+    for (const auto *Child = C->child_begin(); Child != C->child_end();
+         ++Child) {
+      visit(*Child);
+    }
+  }
+
+  void visitTextComment(const comments::TextComment *C) {
+    Out.appendText(C->getText().str());
+    // A paragraph may have multiple TextComments seperated by a newline.
+    // We need to add a space to separate them in the markup::Document.
+    if (C->hasTrailingNewline())
+      Out.appendSpace();
+  }
+
+  void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+
+    if (C->getNumArgs() > 0) {
+      std::string ArgText;
+      for (unsigned I = 0; I < C->getNumArgs(); ++I) {
+        if (!ArgText.empty())
+          ArgText += " ";
+        ArgText += C->getArgText(I);
+      }
+
+      switch (C->getRenderKind()) {
+      case comments::InlineCommandRenderKind::Monospaced:
+        Out.appendCode(ArgText);
+        break;
+      case comments::InlineCommandRenderKind::Bold:
+        Out.appendBoldText(ArgText);
+        break;
+      case comments::InlineCommandRenderKind::Emphasized:
+        Out.appendEmphasizedText(ArgText);
+        break;
+      default:
+        commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
+                        ArgText);
+        break;
+      }
+    } else {
+      commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
+                      "");
+    }
+  }
+
+private:
+  markup::Paragraph &Out;
+  const comments::CommandTraits &Traits;
+};
+
+class BlockCommentToMarkupDocument
+    : public comments::ConstCommentVisitor<BlockCommentToMarkupDocument> {
+public:
+  BlockCommentToMarkupDocument(markup::Document &Out,
+                               const comments::CommandTraits &Traits)
+      : Out(Out), Traits(Traits) {}
+
+  void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+    // Some commands have arguments, like \throws.
+    // The arguments are not part of the paragraph.
+    // We need reconstruct them here.
+    std::string ArgText;
+    for (unsigned I = 0; I < B->getNumArgs(); ++I) {
+      if (!ArgText.empty())
+        ArgText += " ";
+      ArgText += B->getArgText(I);
+    }
+
+    auto &P = Out.addParagraph();
+
+    commandToMarkup(P, B->getCommandName(Traits), B->getCommandMarker(),
+                    ArgText);
+
+    if (!B->getParagraph()->isWhitespace()) {
+      P.appendSpace();
+      ParagraphToMarkupDocument(P, Traits).visit(B->getParagraph());
+    }
+  }
+
+private:
+  markup::Document &Out;
+  const comments::CommandTraits &Traits;
+};
+
+class FullCommentToMarkupDocument
+    : public comments::ConstCommentVisitor<FullCommentToMarkupDocument> {
+public:
+  FullCommentToMarkupDocument(
+      const comments::FullComment &FC, const comments::CommandTraits &Traits,
+      markup::Document &Doc, std::optional<SymbolPrintedType> SymbolType,
+      std::optional<SymbolPrintedType> SymbolReturnType,
+      const std::optional<std::vector<SymbolParam>> &SymbolParameters)
+      : Traits(Traits), Output(Doc) {
+
+    for (auto *Block : FC.getBlocks()) {
+      visit(Block);
+    }
+
+    for (const auto *BP : BriefParagraphs)
+      ParagraphToMarkupDocument(Output.addParagraph(), Traits).visit(BP);
+
+    if (!BriefParagraphs.empty())
+      Output.addRuler();
+
+    if (SymbolReturnType.has_value()) {
+      std::string RT = llvm::to_string(*SymbolReturnType);
+      if (!RT.empty()) {
+        auto &P = Output.addParagraph().appendText("→ ").appendCode(RT);
+        if (!ReturnParagraphs.empty()) {
+          P.appendText(": ");
+          for (const auto *RP : ReturnParagraphs)
+            ParagraphToMarkupDocument(P, Traits).visit(RP);
+        }
+      }
+    }
+
+    if (SymbolParameters.has_value() && !SymbolParameters->empty()) {
+      Output.addParagraph().appendText("Parameters:");
+      markup::BulletList &L = Output.addBulletList();
+      for (const auto &P : *SymbolParameters) {
+        markup::Paragraph &PP =
+            L.addItem().addParagraph().appendCode(llvm::to_string(P));
+
+        if (!P.Name.has_value())
+          continue;
+
+        if (const auto *PCC = Parameters[*P.Name]) {
+          PP.appendText(": ");
+          ParagraphToMarkupDocument(PP, Traits).visit(PCC->getParagraph());
+          Parameters.erase(*P.Name);
+        }
+      }
+    }
+
+    // Don't print Type after Parameters or ReturnType as this will just
+    // duplicate the information
+    if (SymbolType.has_value() && !SymbolReturnType.has_value() &&
+        (!SymbolParameters.has_value() || SymbolParameters->empty())) {
+      Output.addParagraph().appendText("Type: ").appendCode(
+          llvm::to_string(*SymbolType));
+    }
+
+    if (!WarningParagraphs.empty()) {
+      Output.addParagraph().appendText("Warning").appendText(
+          WarningParagraphs.size() > 1 ? "s:" : ":");
+      markup::BulletList &L = Output.addBulletList();
+      for (const auto *WP : WarningParagraphs)
+        ParagraphToMarkupDocument(L.addItem().addParagraph(), Traits).visit(WP);
+      Output.addRuler();
+    }
+
+    if (!NoteParagraphs.empty()) {
+      if (WarningParagraphs.empty())
+        Output.addRuler();
+      Output.addParagraph().appendText("Note").appendText(
+          WarningParagraphs.size() > 1 ? "s:" : ":");
+      markup::BulletList &L = Output.addBulletList();
+      for (const auto *WP : NoteParagraphs)
+        ParagraphToMarkupDocument(L.addItem().addParagraph(), Traits).visit(WP);
+      Output.addRuler();
+    }
+
+    for (unsigned I = 0; I < CommentPartIndex; ++I) {
+      if (const auto *UnhandledCommand = UnhandledCommands.lookup(I)) {
+        BlockCommentToMarkupDocument(Output, Traits).visit(UnhandledCommand);
+        continue;
+      }
+      if (const auto *FreeText = FreeParagraphs.lookup(I)) {
+        ParagraphToMarkupDocument(Output.addParagraph(), Traits)
+            .visit(FreeText);
+        continue;
+      }
+    }
+  }
+
+  void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+    const comments::CommandInfo *Info =
+        Traits.getCommandInfo(B->getCommandID());
+
+    // Visit B->getParagraph() for commands that we have special fields for,
+    // so that the command name won't be included in the string.
+    // Otherwise, we want to keep the command name, so visit B itself.
+    if (Info->IsBriefCommand) {
+      BriefParagraphs.push_back(B->getParagraph());
+    } else if (Info->IsReturnsCommand) {
+      ReturnParagraphs.push_back(B->getParagraph());
+    } else {
+      const llvm::StringRef CommandName = B->getCommandName(Traits);
+      if (CommandName == "warning") {
+        WarningParagraphs.push_back(B->getParagraph());
+      } else if (CommandName == "note") {
+        NoteParagraphs.push_back(B->getParagraph());
+      } else {
+        UnhandledCommands[CommentPartIndex] = B;
+      }
+      CommentPartIndex++;
+    }
+  }
+
+  void visitParagraphComment(const comments::ParagraphComment *P) {
+    FreeParagraphs[CommentPartIndex] = P;
+    CommentPartIndex++;
+  }
+
+  void visitParamCommandComment(const comments::ParamCommandComment *P) {
+    Parameters[P->getParamNameAsWritten()] = P;
+  }
+
+private:
+  const comments::CommandTraits &Traits;
+  markup::Document &Output;
+
+  unsigned CommentPartIndex = 0;
+
+  /// Paragraph of the "brief" command.
+  llvm::SmallVector<const comments::ParagraphComment *, 1> BriefParagraphs;
+
+  /// Paragraph of the "return" command.
+  llvm::SmallVector<const comments::ParagraphComment *, 1> ReturnParagraphs;
+
+  /// 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;
+
+  /// Parsed paragaph(s) of the "param" comamnd(s)
+  llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *>
+      Parameters;
+
+  /// All the paragraphs we don't have any special handling for,
+  /// e.g. "details".
+  llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
+      UnhandledCommands;
+
+  /// All free text paragraphs.
+  llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *>
+      FreeParagraphs;
+};
+
+void fullCommentToMarkupDocument(
+    markup::Document &Doc, const comments::FullComment *FC,
+    const comments::CommandTraits &Traits,
+    std::optional<SymbolPrintedType> SymbolType,
+    std::optional<SymbolPrintedType> SymbolReturnType,
+    const std::optional<std::vector<SymbolParam>> &SymbolParameters) {
+  if (!FC)
+    return;
+  FullCommentToMarkupDocument(*FC, Traits, Doc, SymbolType, SymbolReturnType,
+                              SymbolParameters);
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const SymbolPrintedType &T) {
+  OS << T.Type;
+  if (T.AKA)
+    OS << " (aka " << *T.AKA << ")";
+  return OS;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolParam &P) {
+  if (P.Type)
+    OS << P.Type->Type;
+  if (P.Name)
+    OS << " " << *P.Name;
+  if (P.Default)
+    OS << " = " << *P.Default;
+  if (P.Type && P.Type->AKA)
+    OS << " (aka " << *P.Type->AKA << ")";
+  return OS;
+}
+
+SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
+                            const PrintingPolicy &PP) {
+  // TypePrinter doesn't resolve decltypes, so resolve them here.
+  // FIXME: This doesn't handle composite types that contain a decltype in them.
+  // We should rather have a printing policy for that.
+  while (!QT.isNull() && QT->isDecltypeType())
+    QT = QT->castAs<DecltypeType>()->getUnderlyingType();
+  SymbolPrintedType Result;
+  llvm::raw_string_ostream OS(Result.Type);
+  // Special case: if the outer type is a tag type without qualifiers, then
+  // include the tag for extra clarity.
+  // This isn't very idiomatic, so don't attempt it for complex cases, including
+  // pointers/references, template specializations, etc.
+  if (!QT.isNull() && !QT.hasQualifiers() && PP.SuppressTagKeyword) {
+    if (auto *TT = llvm::dyn_cast<TagType>(QT.getTypePtr()))
+      OS << TT->getDecl()->getKindName() << " ";
+  }
+  QT.print(OS, PP);
+
+  const Config &Cfg = Config::current();
+  if (!QT.isNull() && Cfg.Hover.ShowAKA) {
+    bool ShouldAKA = false;
+    QualType DesugaredTy = clang::desugarForDiagnostic(ASTCtx, QT, ShouldAKA);
+    if (ShouldAKA)
+      Result.AKA = DesugaredTy.getAsString(PP);
+  }
+  return Result;
+}
+
+SymbolPrintedType printType(const TemplateTypeParmDecl *TTP) {
+  SymbolPrintedType Result;
+  Result.Type = TTP->wasDeclaredWithTypename() ? "typename" : "class";
+  if (TTP->isParameterPack())
+    Result.Type += "...";
+  return Result;
+}
+
+SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
+                            const PrintingPolicy &PP) {
+  auto PrintedType = printType(NTTP->getType(), NTTP->getASTContext(), PP);
+  if (NTTP->isParameterPack()) {
+    PrintedType.Type += "...";
+    if (PrintedType.AKA)
+      *PrintedType.AKA += "...";
+  }
+  return PrintedType;
+}
+
+SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
+                            const PrintingPolicy &PP) {
+  SymbolPrintedType Result;
+  llvm::raw_string_ostream OS(Result.Type);
+  OS << "template <";
+  llvm::StringRef Sep = "";
+  for (const Decl *Param : *TTP->getTemplateParameters()) {
+    OS << Sep;
+    Sep = ", ";
+    if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param))
+      OS << printType(TTP).Type;
+    else if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param))
+      OS << printType(NTTP, PP).Type;
+    else if (const auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(Param))
+      OS << printType(TTPD, PP).Type;
+  }
+  // FIXME: TemplateTemplateParameter doesn't store the info on whether this
+  // param was a "typename" or "class".
+  OS << "> class";
+  return Result;
+}
+
+// Default argument might exist but be unavailable, in the case of unparsed
+// arguments for example. This function returns the default argument if it is
+// available.
+const Expr *getDefaultArg(const ParmVarDecl *PVD) {
+  // Default argument can be unparsed or uninstantiated. For the former we
+  // can't do much, as token information is only stored in Sema and not
+  // attached to the AST node. For the latter though, it is safe to proceed as
+  // the expression is still valid.
+  if (!PVD->hasDefaultArg() || PVD->hasUnparsedDefaultArg())
+    return nullptr;
+  return PVD->hasUninstantiatedDefaultArg() ? PVD->getUninstantiatedDefaultArg()
+                                            : PVD->getDefaultArg();
+}
+
+SymbolParam createSymbolParam(const ParmVarDecl *PVD,
+                              const PrintingPolicy &PP) {
+  SymbolParam Out;
+  Out.Type = printType(PVD->getType(), PVD->getASTContext(), PP);
+  if (!PVD->getName().empty())
+    Out.Name = PVD->getNameAsString();
+  if (const Expr *DefArg = getDefaultArg(PVD)) {
+    Out.Default.emplace();
+    llvm::raw_string_ostream OS(*Out.Default);
+    DefArg->printPretty(OS, nullptr, PP);
+  }
+  return Out;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h
new file mode 100644
index 0000000000000..6d53edf2dc129
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -0,0 +1,88 @@
+//===--- SymbolDocumentation.h ==---------------------------------*- C++-*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Class to parse doxygen comments into a flat structure for consumption
+// in e.g. Hover and Code Completion
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
+
+#include "support/Markup.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclTemplate.h"
+#include "llvm/Support/raw_ostream.h"
+#include <optional>
+#include <string>
+#include <tuple>
+
+namespace clang {
+namespace clangd {
+
+/// Contains pretty-printed type and desugared type
+struct SymbolPrintedType {
+  SymbolPrintedType() = default;
+  SymbolPrintedType(const char *Type) : Type(Type) {}
+  SymbolPrintedType(const char *Type, const char *AKAType)
+      : Type(Type), AKA(AKAType) {}
+
+  /// Pretty-printed type
+  std::string Type;
+  /// Desugared type
+  std::optional<std::string> AKA;
+};
+
+/// Represents parameters of a function, a template or a macro.
+/// For example:
+/// - void foo(ParamType Name = DefaultValue)
+/// - #define FOO(Name)
+/// - template <ParamType Name = DefaultType> class Foo {};
+struct SymbolParam {
+  /// The printable parameter type, e.g. "int", or "typename" (in
+  /// TemplateParameters), might be std::nullopt for macro parameters.
+  std::optional<SymbolPrintedType> Type;
+  /// std::nullopt for unnamed parameters.
+  std::optional<std::string> Name;
+  /// std::nullopt if no default is provided.
+  std::optional<std::string> Default;
+};
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const SymbolPrintedType &T);
+inline bool operator==(const SymbolPrintedType &LHS,
+                       const SymbolPrintedType &RHS) {
+  return std::tie(LHS.Type, LHS.AKA) == std::tie(RHS.Type, RHS.AKA);
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolParam &);
+inline bool operator==(const SymbolParam &LHS, const SymbolParam &RHS) {
+  return std::tie(LHS.Type, LHS.Name, LHS.Default) ==
+         std::tie(RHS.Type, RHS.Name, RHS.Default);
+}
+
+SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
+                            const PrintingPolicy &PP);
+SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
+                            const PrintingPolicy &PP);
+SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
+                            const PrintingPolicy &PP);
+
+SymbolParam createSymbolParam(const ParmVarDecl *PVD, const PrintingPolicy &PP);
+
+void fullCommentToMarkupDocument(
+    markup::Document &Doc, const comments::FullComment *FC,
+    const comments::CommandTraits &Traits,
+    std::optional<SymbolPrintedType> SymbolType,
+    std::optional<SymbolPrintedType> SymbolReturnType,
+    const std::optional<std::vector<SymbolParam>> &SymbolParameters);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
diff --git a/clang-tools-extra/clangd/support/Markup.cpp b/clang-tools-extra/clangd/support/Markup.cpp
index 63aff96b02056..d26b33995e452 100644
--- a/clang-tools-extra/clangd/support/Markup.cpp
+++ b/clang-tools-extra/clangd/support/Markup.cpp
@@ -433,6 +433,20 @@ Paragraph &Paragraph::appendText(llvm::StringRef Text) {
   return *this;
 }
 
+Paragraph &Paragraph::appendEmphasizedText(llvm::StringRef Text) {
+  std::string ItalicText = "*" + canonicalizeSpaces(Text) + "*";
+  if (ItalicText.size() == 2)
+    return *this;
+  return appendText(ItalicText);
+}
+
+Paragraph &Paragraph::appendBoldText(llvm::StringRef Text) {
+  std::string BoldText = "**" + canonicalizeSpaces(Text) + "**";
+  if (BoldText.size() == 4)
+    return *this;
+  return appendText(BoldText);
+}
+
 Paragraph &Paragraph::appendCode(llvm::StringRef Code, bool Preserve) {
   bool AdjacentCode =
       !Chunks.empty() && Chunks.back().Kind == Chunk::InlineCode;
diff --git a/clang-tools-extra/clangd/support/Markup.h b/clang-tools-extra/clangd/support/Markup.h
index 3a869c49a2cbb..70b98366ea9a3 100644
--- a/clang-tools-extra/clangd/support/Markup.h
+++ b/clang-tools-extra/clangd/support/Markup.h
@@ -49,6 +49,12 @@ class Paragraph : public Block {
   /// Append plain text to the end of the string.
   Paragraph &appendText(llvm::StringRef Text);
 
+  /// Append emphasized text, this translates to the * block in markdown.
+  Paragraph &appendEmphasizedText(llvm::StringRef Text);
+
+  /// Append bold text, this translates to the ** block in markdown.
+  Paragraph &appendBoldText(llvm::StringRef Text);
+
   /// Append inline code, this translates to the ` block in markdown.
   /// \p Preserve indicates the code span must be apparent even in plaintext.
   Paragraph &appendCode(llvm::StringRef Code, bool Preserve = false);
diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
index de5f533d31645..9db9d9f0fb255 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
@@ -11,6 +11,7 @@
 #include "clang/Sema/CodeCompleteConsumer.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include <optional>
 
 namespace clang {
 namespace clangd {
@@ -236,6 +237,224 @@ TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) {
   EXPECT_EQ(Snippet, "${1:(type2)}");
 }
 
+TEST(CompletionString, ParseDocumentation) {
+
+  llvm::BumpPtrAllocator Allocator;
+  CommentOptions CommentOpts;
+  comments::CommandTraits Traits(Allocator, CommentOpts);
+
+  struct Case {
+    llvm::StringRef Documentation;
+    std::optional<SymbolPrintedType> SymbolType;
+    std::optional<SymbolPrintedType> SymbolReturnType;
+    std::optional<std::vector<SymbolParam>> SymbolParameters;
+    llvm::StringRef ExpectedRenderMarkdown;
+    llvm::StringRef ExpectedRenderPlainText;
+  } Cases[] = {
+      {
+          "foo bar",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo bar",
+          "foo bar",
+      },
+      {
+          "foo\nbar\n",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo bar",
+          "foo bar",
+      },
+      {
+          "foo\n\nbar\n",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo  \nbar",
+          "foo\nbar",
+      },
+      {
+          "foo \\p bar baz",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo `bar` baz",
+          "foo bar baz",
+      },
+      {
+          "foo \\e bar baz",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo \\*bar\\* baz",
+          "foo *bar* baz",
+      },
+      {
+          "foo \\b bar baz",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo \\*\\*bar\\*\\* baz",
+          "foo **bar** baz",
+      },
+      {
+          "foo \\ref bar baz",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo \\*\\*\\\\ref\\*\\* \\*bar\\* baz",
+          "foo **\\ref** *bar* baz",
+      },
+      {
+          "foo @ref bar baz",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "foo \\*\\*@ref\\*\\* \\*bar\\* baz",
+          "foo **@ref** *bar* baz",
+      },
+      {
+          "\\throw exception foo",
+          std::nullopt,
+          std::nullopt,
+          std::nullopt,
+          "\\*\\*\\\\throw\\*\\* \\*exception\\* foo",
+          "**\\throw** *exception* foo",
+      },
+      {"@throws exception foo\n\n at note bar\n\n at warning baz\n\n at details "
+       "qux\n\nfree standing paragraph",
+       std::nullopt, std::nullopt, std::nullopt,
+       "Warning:  \n- baz\n\n---\n"
+       "Note:  \n- bar\n\n---\n"
+       "\\*\\*@throws\\*\\* \\*exception\\* foo  \n"
+       "\\*\\*@details\\*\\* qux  \n"
+       "free standing paragraph",
+       "Warning:\n- baz\n\n"
+       "Note:\n- bar\n\n"
+       "**@throws** *exception* foo\n"
+       "**@details** qux\n"
+       "free standing paragraph"},
+      {"@throws exception foo\n\n at note bar\n\n at warning baz\n\n at note another "
+       "note\n\n at warning another warning\n\n at details qux\n\nfree standing "
+       "paragraph",
+       std::nullopt, std::nullopt, std::nullopt,
+       "Warnings:  \n- baz\n- another warning\n\n---\n"
+       "Notes:  \n- bar\n- another note\n\n---\n"
+       "\\*\\*@throws\\*\\* \\*exception\\* foo  \n"
+       "\\*\\*@details\\*\\* qux  \n"
+       "free standing paragraph",
+       "Warnings:\n- baz\n- another warning\n\n"
+       "Notes:\n- bar\n- another note\n\n"
+       "**@throws** *exception* foo\n"
+       "**@details** qux\n"
+       "free standing paragraph"},
+      {
+          "",
+          SymbolPrintedType("my_type", "type"),
+          std::nullopt,
+          std::nullopt,
+          "Type: `my_type (aka type)`",
+          "Type: my_type (aka type)",
+      },
+      {
+          "",
+          SymbolPrintedType("my_type", "type"),
+          SymbolPrintedType("my_ret_type", "type"),
+          std::nullopt,
+          "→ `my_ret_type (aka type)`",
+          "→ my_ret_type (aka type)",
+      },
+      {
+          "\\return foo",
+          SymbolPrintedType("my_type", "type"),
+          SymbolPrintedType("my_ret_type", "type"),
+          std::nullopt,
+          "→ `my_ret_type (aka type)`: foo",
+          "→ my_ret_type (aka type): foo",
+      },
+      {
+          "\\returns foo",
+          SymbolPrintedType("my_type", "type"),
+          SymbolPrintedType("my_ret_type", "type"),
+          std::nullopt,
+          "→ `my_ret_type (aka type)`: foo",
+          "→ my_ret_type (aka type): foo",
+      },
+      {
+          "",
+          std::nullopt,
+          std::nullopt,
+          std::vector<SymbolParam>{
+              {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+          "Parameters:  \n- `my_type foo = default (aka type)`",
+          "Parameters:\n- my_type foo = default (aka type)",
+      },
+      {
+          "\\param foo bar",
+          std::nullopt,
+          std::nullopt,
+          std::vector<SymbolParam>{
+              {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+          "Parameters:  \n- `my_type foo = default (aka type)`: bar",
+          "Parameters:\n- my_type foo = default (aka type): bar",
+      },
+      {
+          "\\param foo bar\n\n\\param baz qux",
+          std::nullopt,
+          std::nullopt,
+          std::vector<SymbolParam>{
+              {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+          "Parameters:  \n- `my_type foo = default (aka type)`: bar",
+          "Parameters:\n- my_type foo = default (aka type): bar",
+      },
+      {
+          "\\brief This is a brief description\n\nlonger description "
+          "paragraph\n\n\\note foo\n\n\\warning warning\n\njust another "
+          "paragraph\\param foo bar\\return baz",
+          SymbolPrintedType("my_type", "type"),
+          SymbolPrintedType("my_ret_type", "type"),
+          std::vector<SymbolParam>{
+              {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+          "This is a brief description  \n\n"
+          "---\n"
+          "→ `my_ret_type (aka type)`: baz  \n"
+          "Parameters:  \n"
+          "- `my_type foo = default (aka type)`: bar\n\n"
+          "Warning:  \n"
+          "- warning\n\n"
+          "---\n"
+          "Note:  \n"
+          "- foo\n\n"
+          "---\n"
+          "longer description paragraph  \n"
+          "just another paragraph",
+
+          R"(This is a brief description
+
+→ my_ret_type (aka type): baz
+Parameters:
+- my_type foo = default (aka type): bar
+Warning:
+- warning
+
+Note:
+- foo
+
+longer description paragraph
+just another paragraph)",
+      }};
+
+  for (const auto &C : Cases) {
+    markup::Document Doc;
+    docCommentToMarkup(Doc, C.Documentation, Allocator, Traits, C.SymbolType,
+                       C.SymbolReturnType, C.SymbolParameters);
+    EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
+    EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
+  }
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 69f6df46c87ce..b797d65be032c 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -10,6 +10,7 @@
 #include "Annotations.h"
 #include "Config.h"
 #include "Hover.h"
+#include "SymbolDocumentation.h"
 #include "TestFS.h"
 #include "TestIndex.h"
 #include "TestTU.h"
@@ -639,7 +640,7 @@ class Foo final {})cpp";
        [](HoverInfo &HI) {
          HI.Name = "DECL_STR";
          HI.Kind = index::SymbolKind::Macro;
-         HI.Type = HoverInfo::PrintedType("const char *");
+         HI.Type = SymbolPrintedType("const char *");
          HI.Definition = "#define DECL_STR(NAME, VALUE) const char *v_##NAME = "
                          "STRINGIFY(VALUE)\n\n"
                          "// Expands to\n"
@@ -1848,7 +1849,7 @@ TEST(Hover, All) {
             HI.Type = "int ()";
             HI.Definition = "int x()";
             HI.ReturnType = "int";
-            HI.Parameters = std::vector<HoverInfo::Param>{};
+            HI.Parameters = std::vector<SymbolParam>{};
           }},
       {
           R"cpp(// Static method call
@@ -1865,7 +1866,7 @@ TEST(Hover, All) {
             HI.Type = "int ()";
             HI.Definition = "static int x()";
             HI.ReturnType = "int";
-            HI.Parameters = std::vector<HoverInfo::Param>{};
+            HI.Parameters = std::vector<SymbolParam>{};
           }},
       {
           R"cpp(// Typedef
@@ -1944,7 +1945,7 @@ TEST(Hover, All) {
             HI.Definition = "void foo()";
             HI.Documentation = "";
             HI.ReturnType = "void";
-            HI.Parameters = std::vector<HoverInfo::Param>{};
+            HI.Parameters = std::vector<SymbolParam>{};
           }},
       {
           R"cpp( // using declaration and two possible function declarations
@@ -2032,7 +2033,7 @@ TEST(Hover, All) {
             HI.Definition = "void foo()";
             HI.Documentation = "Function declaration";
             HI.ReturnType = "void";
-            HI.Parameters = std::vector<HoverInfo::Param>{};
+            HI.Parameters = std::vector<SymbolParam>{};
           }},
       {
           R"cpp(// Enum declaration
@@ -2173,7 +2174,7 @@ TEST(Hover, All) {
             HI.Definition = "template <> int foo<int>()";
             HI.Documentation = "Templated function";
             HI.ReturnType = "int";
-            HI.Parameters = std::vector<HoverInfo::Param>{};
+            HI.Parameters = std::vector<SymbolParam>{};
             // FIXME: We should populate template parameters with arguments in
             // case of instantiations.
           }},
@@ -2207,7 +2208,7 @@ TEST(Hover, All) {
             HI.Type = "void ()";
             HI.Definition = "void indexSymbol()";
             HI.ReturnType = "void";
-            HI.Parameters = std::vector<HoverInfo::Param>{};
+            HI.Parameters = std::vector<SymbolParam>{};
             HI.Documentation = "comment from index";
           }},
       {
@@ -3440,8 +3441,8 @@ TEST(Hover, Present) {
           },
           R"(class foo
 
-Size: 10 bytes
 documentation
+Size: 10 bytes
 
 template <typename T, typename C = bool> class Foo {})",
       },
@@ -3452,7 +3453,7 @@ template <typename T, typename C = bool> class Foo {})",
             HI.Type = {"type", "c_type"};
             HI.ReturnType = {"ret_type", "can_ret_type"};
             HI.Parameters.emplace();
-            HoverInfo::Param P;
+            SymbolParam P;
             HI.Parameters->push_back(P);
             P.Type = {"type", "can_type"};
             HI.Parameters->push_back(P);
@@ -3955,7 +3956,7 @@ TEST(Hover, DisableShowAKA) {
   auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
 
   ASSERT_TRUE(H);
-  EXPECT_EQ(H->Type, HoverInfo::PrintedType("m_int"));
+  EXPECT_EQ(H->Type, SymbolPrintedType("m_int"));
 }
 
 TEST(Hover, HideBigInitializers) {
@@ -4093,7 +4094,7 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
           /*Validator=*/
           [](std::optional<HoverInfo> HI, size_t) {
             EXPECT_EQ(HI->Value, "42 (0x2a)");
-            EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int"));
+            EXPECT_EQ(HI->Type, SymbolPrintedType("int"));
           },
       },
       {
@@ -4184,7 +4185,7 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
           [](std::optional<HoverInfo> HI, size_t Id) {
             switch (Id) {
             case 0:
-              EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int[10]"));
+              EXPECT_EQ(HI->Type, SymbolPrintedType("int[10]"));
               break;
             case 1:
             case 2:
@@ -4210,11 +4211,11 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
             switch (Id) {
             case 0:
               EXPECT_FALSE(HI->Value);
-              EXPECT_EQ(HI->Type, HoverInfo::PrintedType("const (lambda)"));
+              EXPECT_EQ(HI->Type, SymbolPrintedType("const (lambda)"));
               break;
             case 1:
               EXPECT_EQ(HI->Value, "0");
-              EXPECT_EQ(HI->Type, HoverInfo::PrintedType("u64"));
+              EXPECT_EQ(HI->Type, SymbolPrintedType("u64"));
               break;
             case 2:
               EXPECT_FALSE(HI);
diff --git a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
index 2d86c91c7ec08..43d015c15bd43 100644
--- a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
+++ b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
@@ -210,6 +210,36 @@ TEST(Paragraph, NewLines) {
   EXPECT_EQ(P.asPlainText(), "foo bar foo bar");
 }
 
+TEST(Paragraph, BoldText) {
+  Paragraph P;
+  P.appendBoldText("");
+  EXPECT_EQ(P.asMarkdown(), "");
+  EXPECT_EQ(P.asPlainText(), "");
+
+  P.appendBoldText(" \n foo\nbar\n ");
+  EXPECT_EQ(P.asMarkdown(), "\\*\\*foo bar\\*\\*");
+  EXPECT_EQ(P.asPlainText(), "**foo bar**");
+
+  P.appendSpace().appendBoldText("foobar");
+  EXPECT_EQ(P.asMarkdown(), "\\*\\*foo bar\\*\\* \\*\\*foobar\\*\\*");
+  EXPECT_EQ(P.asPlainText(), "**foo bar** **foobar**");
+}
+
+TEST(Paragraph, EmphasizedText) {
+  Paragraph P;
+  P.appendEmphasizedText("");
+  EXPECT_EQ(P.asMarkdown(), "");
+  EXPECT_EQ(P.asPlainText(), "");
+
+  P.appendEmphasizedText(" \n foo\nbar\n ");
+  EXPECT_EQ(P.asMarkdown(), "\\*foo bar\\*");
+  EXPECT_EQ(P.asPlainText(), "*foo bar*");
+
+  P.appendSpace().appendEmphasizedText("foobar");
+  EXPECT_EQ(P.asMarkdown(), "\\*foo bar\\* \\*foobar\\*");
+  EXPECT_EQ(P.asPlainText(), "*foo bar* *foobar*");
+}
+
 TEST(Document, Separators) {
   Document D;
   D.addParagraph().appendText("foo");
diff --git a/clang/include/clang/AST/Comment.h b/clang/include/clang/AST/Comment.h
index dd9906727293f..8a0216bf51a30 100644
--- a/clang/include/clang/AST/Comment.h
+++ b/clang/include/clang/AST/Comment.h
@@ -19,6 +19,7 @@
 #include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Compiler.h"
 
 namespace clang {
 class Decl;
@@ -119,6 +120,11 @@ class Comment {
 
     LLVM_PREFERRED_TYPE(CommandTraits::KnownCommandIDs)
     unsigned CommandID : CommandInfo::NumCommandIDBits;
+
+    /// Describes the syntax that was used in a documentation command.
+    /// Contains values from CommandMarkerKind enum.
+    LLVM_PREFERRED_TYPE(CommandMarkerKind)
+    unsigned CommandMarker : 1;
   };
   enum { NumInlineCommandCommentBits = NumInlineContentCommentBits + 3 +
                                        CommandInfo::NumCommandIDBits };
@@ -347,6 +353,17 @@ class InlineCommandComment : public InlineContentComment {
     InlineCommandCommentBits.RenderKind = llvm::to_underlying(RK);
     InlineCommandCommentBits.CommandID = CommandID;
   }
+  InlineCommandComment(SourceLocation LocBegin, SourceLocation LocEnd,
+                       unsigned CommandID, InlineCommandRenderKind RK,
+                       CommandMarkerKind CommandMarker,
+                       ArrayRef<Argument> Args)
+      : InlineContentComment(CommentKind::InlineCommandComment, LocBegin,
+                             LocEnd),
+        Args(Args) {
+    InlineCommandCommentBits.RenderKind = llvm::to_underlying(RK);
+    InlineCommandCommentBits.CommandID = CommandID;
+    InlineCommandCommentBits.CommandMarker = llvm::to_underlying(CommandMarker);
+  }
 
   static bool classof(const Comment *C) {
     return C->getCommentKind() == CommentKind::InlineCommandComment;
@@ -384,6 +401,11 @@ class InlineCommandComment : public InlineContentComment {
   SourceRange getArgRange(unsigned Idx) const {
     return Args[Idx].Range;
   }
+
+  CommandMarkerKind getCommandMarker() const LLVM_READONLY {
+    return static_cast<CommandMarkerKind>(
+        InlineCommandCommentBits.CommandMarker);
+  }
 };
 
 /// Abstract class for opening and closing HTML tags.  HTML tags are always
diff --git a/clang/include/clang/AST/CommentSema.h b/clang/include/clang/AST/CommentSema.h
index 916d7945329c5..3169e2b0d86b9 100644
--- a/clang/include/clang/AST/CommentSema.h
+++ b/clang/include/clang/AST/CommentSema.h
@@ -131,6 +131,7 @@ class Sema {
   InlineCommandComment *actOnInlineCommand(SourceLocation CommandLocBegin,
                                            SourceLocation CommandLocEnd,
                                            unsigned CommandID,
+                                           CommandMarkerKind CommandMarker,
                                            ArrayRef<Comment::Argument> Args);
 
   InlineContentComment *actOnUnknownCommand(SourceLocation LocBegin,
diff --git a/clang/lib/AST/CommentParser.cpp b/clang/lib/AST/CommentParser.cpp
index 12ed8e3f1b79a..a3c22f81eb590 100644
--- a/clang/lib/AST/CommentParser.cpp
+++ b/clang/lib/AST/CommentParser.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/CommentParser.h"
+#include "clang/AST/Comment.h"
 #include "clang/AST/CommentCommandTraits.h"
 #include "clang/AST/CommentSema.h"
 #include "clang/Basic/CharInfo.h"
@@ -569,6 +570,7 @@ BlockCommandComment *Parser::parseBlockCommand() {
 
 InlineCommandComment *Parser::parseInlineCommand() {
   assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
+  CommandMarkerKind CMK = Tok.is(tok::backslash_command) ? CMK_Backslash : CMK_At;
   const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
 
   const Token CommandTok = Tok;
@@ -580,7 +582,8 @@ InlineCommandComment *Parser::parseInlineCommand() {
 
   InlineCommandComment *IC = S.actOnInlineCommand(
       CommandTok.getLocation(), CommandTok.getEndLocation(),
-      CommandTok.getCommandID(), Args);
+      CommandTok.getCommandID(),
+      CMK, Args);
 
   if (Args.size() < Info->NumArgs) {
     Diag(CommandTok.getEndLocation().getLocWithOffset(1),
diff --git a/clang/lib/AST/CommentSema.cpp b/clang/lib/AST/CommentSema.cpp
index bd2206bb8a3bc..ca3813b8c891d 100644
--- a/clang/lib/AST/CommentSema.cpp
+++ b/clang/lib/AST/CommentSema.cpp
@@ -360,12 +360,13 @@ void Sema::actOnTParamCommandFinish(TParamCommandComment *Command,
 InlineCommandComment *
 Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
                          SourceLocation CommandLocEnd, unsigned CommandID,
+                         CommandMarkerKind CommandMarker,
                          ArrayRef<Comment::Argument> Args) {
   StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
 
   return new (Allocator)
       InlineCommandComment(CommandLocBegin, CommandLocEnd, CommandID,
-                           getInlineCommandRenderKind(CommandName), Args);
+                           getInlineCommandRenderKind(CommandName), CommandMarker, Args);
 }
 
 InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
index b609d4a7462fb..f8c4838ab7ee3 100644
--- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
@@ -122,6 +122,7 @@ static_library("clangd") {
     "SemanticHighlighting.cpp",
     "SemanticSelection.cpp",
     "SourceCode.cpp",
+    "SymbolDocumentation.cpp",
     "SystemIncludeExtractor.cpp",
     "TUScheduler.cpp",
     "TidyProvider.cpp",



More information about the cfe-commits mailing list