[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
Tue Mar 11 12:01:54 PDT 2025


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

>From 18fa9e58b04b4e41e2b810fcc4a0c6be48258a1f Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Tue, 11 Mar 2025 19:00:49 +0000
Subject: [PATCH] [clangd] Add doxygen parsing for hover information

---
 clang-tools-extra/clangd/CMakeLists.txt       |   1 +
 .../clangd/CodeCompletionStrings.cpp          |  74 ++-
 .../clangd/CodeCompletionStrings.h            |  23 +
 clang-tools-extra/clangd/Hover.cpp            | 135 ++---
 clang-tools-extra/clangd/Hover.h              |  54 +-
 .../clangd/SymbolDocumentation.cpp            | 503 ++++++++++++++++++
 .../clangd/SymbolDocumentation.h              |  92 ++++
 clang-tools-extra/clangd/support/Markup.cpp   |  36 ++
 clang-tools-extra/clangd/support/Markup.h     |   8 +
 .../unittests/CodeCompletionStringsTests.cpp  | 219 ++++++++
 .../clangd/unittests/HoverTests.cpp           | 127 ++++-
 .../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, 1177 insertions(+), 157 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..f307866176ca5 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -9,6 +9,12 @@
 #include "CodeCompletionStrings.h"
 #include "clang-c/Index.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentLexer.h"
+#include "clang/AST/CommentParser.h"
+#include "clang/AST/CommentSema.h"
+#include "clang/AST/Decl.h"
 #include "clang/AST/RawCommentList.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
@@ -100,14 +106,25 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
     // the comments for namespaces.
     return "";
   }
-  const RawComment *RC = getCompletionComment(Ctx, &Decl);
-  if (!RC)
-    return "";
-  // Sanity check that the comment does not come from the PCH. We choose to not
-  // write them into PCH, because they are racy and slow to load.
-  assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
-  std::string Doc =
-      RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+
+  std::string Doc;
+
+  if (isa<ParmVarDecl>(Decl)) {
+    // Parameters are documented in the function comment.
+    if (const auto *FD = dyn_cast<FunctionDecl>(Decl.getDeclContext()))
+      Doc = getParamDocString(Ctx.getCommentForDecl(FD, nullptr),
+                              Decl.getName(), Ctx.getCommentCommandTraits());
+  } else {
+
+    const RawComment *RC = getCompletionComment(Ctx, &Decl);
+    if (!RC)
+      return "";
+    // Sanity check that the comment does not come from the PCH. We choose to
+    // not write them into PCH, because they are racy and slow to load.
+    assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
+    Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+  }
+
   if (!looksLikeDocComment(Doc))
     return "";
   // Clang requires source to be UTF-8, but doesn't enforce this in comments.
@@ -316,5 +333,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..771f1d99e639e
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -0,0 +1,503 @@
+//===--- 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);
+}
+
+class ParamDocStringVisitor
+    : public comments::ConstCommentVisitor<ParamDocStringVisitor> {
+public:
+  ParamDocStringVisitor(const comments::FullComment *FC, StringRef ParamName,
+                        const comments::CommandTraits &Traits)
+      : ParamName(ParamName), Traits(Traits) {
+    if (!FC)
+      return;
+
+    for (const auto *Block : FC->getBlocks()) {
+      visit(Block);
+      if (ParamCommentFound)
+        break;
+    }
+  }
+
+  void visitParamCommandComment(const comments::ParamCommandComment *P) {
+    if (P->getParamNameAsWritten() == ParamName) {
+      ParamCommentFound = true;
+      visit(P->getParagraph());
+    }
+  }
+
+  void visitParagraphComment(const comments::ParagraphComment *C) {
+    if (!ParamCommentFound)
+      return;
+    for (const auto *Child = C->child_begin(); Child != C->child_end();
+         ++Child) {
+      visit(*Child);
+    }
+  }
+
+  void visitTextComment(const comments::TextComment *C) {
+    if (!ParamCommentFound)
+      return;
+    if (Result.empty())
+      // When hovering over the parameter, the documentation is parsed the same
+      // way as any other comment. This means that the brief command is shown
+      // more prominently as the paragraph in the hover.
+      // Hence we add the brief command to the beginning of the documentation to
+      // reuse the same behaviour.
+      Result = "\\brief";
+
+    Result += " " + C->getText().trim().str();
+  }
+
+  void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+    if (!ParamCommentFound)
+      return;
+
+    if (Result.empty())
+      // When hovering over the parameter, the documentation is parsed the same
+      // way as any other comment. This means that the brief command is shown
+      // more prominently as the paragraph in the hover.
+      // Hence we add the brief command to the beginning of the documentation to
+      // reuse the same behaviour.
+      Result = "\\brief";
+
+    Result += " ";
+    Result += C->getCommandMarker() == (comments::CommandMarkerKind::CMK_At)
+                  ? "@"
+                  : "\\" + C->getCommandName(Traits).str();
+
+    if (C->getNumArgs() > 0) {
+      for (unsigned I = 0; I < C->getNumArgs(); ++I) {
+        Result += " " + C->getArgText(I).str();
+      }
+    }
+  }
+
+  StringRef getParamDocString() const { return Result; }
+
+private:
+  StringRef ParamName;
+  std::string Result;
+  bool ParamCommentFound = false;
+  const comments::CommandTraits &Traits;
+};
+
+std::string getParamDocString(const comments::FullComment *FC,
+                              StringRef ParamName,
+                              const comments::CommandTraits &Traits) {
+  if (!FC)
+    return "";
+  ParamDocStringVisitor PDSV(FC, ParamName, Traits);
+  return PDSV.getParamDocString().str();
+}
+
+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..7da2fffb51e05
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -0,0 +1,92 @@
+//===--- 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);
+
+std::string getParamDocString(const comments::FullComment *FC,
+                              StringRef ParamName,
+                              const comments::CommandTraits &Traits);
+
+} // 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..d025f078c6d8d 100644
--- a/clang-tools-extra/clangd/support/Markup.cpp
+++ b/clang-tools-extra/clangd/support/Markup.cpp
@@ -354,6 +354,12 @@ void Paragraph::renderMarkdown(llvm::raw_ostream &OS) const {
     case Chunk::InlineCode:
       OS << renderInlineBlock(C.Contents);
       break;
+    case Chunk::Bold:
+      OS << "**" << renderText(C.Contents, !HasChunks) << "**";
+      break;
+    case Chunk::Emphasized:
+      OS << "*" << renderText(C.Contents, !HasChunks) << "*";
+      break;
     }
     HasChunks = true;
     NeedsSpace = C.SpaceAfter;
@@ -387,6 +393,10 @@ void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
     llvm::StringRef Marker = "";
     if (C.Preserve && C.Kind == Chunk::InlineCode)
       Marker = chooseMarker({"`", "'", "\""}, C.Contents);
+    else if (C.Kind == Chunk::Bold)
+      Marker = "**";
+    else if (C.Kind == Chunk::Emphasized)
+      Marker = "*";
     OS << Marker << C.Contents << Marker;
     NeedsSpace = C.SpaceAfter;
   }
@@ -433,6 +443,32 @@ Paragraph &Paragraph::appendText(llvm::StringRef Text) {
   return *this;
 }
 
+Paragraph &Paragraph::appendEmphasizedText(llvm::StringRef Text) {
+  std::string Norm = canonicalizeSpaces(Text);
+  if (Norm.empty())
+    return *this;
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Norm);
+  C.Kind = Chunk::Emphasized;
+  C.SpaceBefore = llvm::isSpace(Text.front());
+  C.SpaceAfter = llvm::isSpace(Text.back());
+  return *this;
+}
+
+Paragraph &Paragraph::appendBoldText(llvm::StringRef Text) {
+  std::string Norm = canonicalizeSpaces(Text);
+  if (Norm.empty())
+    return *this;
+  Chunks.emplace_back();
+  Chunk &C = Chunks.back();
+  C.Contents = std::move(Norm);
+  C.Kind = Chunk::Bold;
+  C.SpaceBefore = llvm::isSpace(Text.front());
+  C.SpaceAfter = llvm::isSpace(Text.back());
+  return *this;
+}
+
 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..de327b459f957 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);
@@ -62,6 +68,8 @@ class Paragraph : public Block {
     enum {
       PlainText,
       InlineCode,
+      Bold,
+      Emphasized
     } Kind = PlainText;
     // Preserve chunk markers in plaintext.
     bool Preserve = false;
diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
index de5f533d31645..81a14e069bd82 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..fdb3bfa0797b2 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);
@@ -4264,6 +4265,104 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
   EXPECT_TRUE(H->Value);
   EXPECT_TRUE(H->Type);
 }
+
+TEST(Hover, FunctionParameters) {
+  struct {
+    const char *const Code;
+    const std::function<void(HoverInfo &)> ExpectedBuilder;
+  } Cases[] = {
+      {R"cpp(/// Function doc
+      void foo(int [[^a]]);
+    )cpp",
+       [](HoverInfo &HI) {
+         HI.Name = "a";
+         HI.Kind = index::SymbolKind::Parameter;
+         HI.NamespaceScope = "";
+         HI.LocalScope = "foo::";
+         HI.Type = "int";
+         HI.Definition = "int a";
+         HI.Documentation = "";
+       }},
+      {R"cpp(/// Function doc
+      /// @param a this is doc for a
+      void foo(int [[^a]]);
+    )cpp",
+       [](HoverInfo &HI) {
+         HI.Name = "a";
+         HI.Kind = index::SymbolKind::Parameter;
+         HI.NamespaceScope = "";
+         HI.LocalScope = "foo::";
+         HI.Type = "int";
+         HI.Definition = "int a";
+         HI.Documentation = "\\brief this is doc for a";
+       }},
+      {R"cpp(/// Function doc
+      /// @param b this is doc for b
+      void foo(int [[^a]], int b);
+    )cpp",
+       [](HoverInfo &HI) {
+         HI.Name = "a";
+         HI.Kind = index::SymbolKind::Parameter;
+         HI.NamespaceScope = "";
+         HI.LocalScope = "foo::";
+         HI.Type = "int";
+         HI.Definition = "int a";
+         HI.Documentation = "";
+       }},
+      {R"cpp(/// Function doc
+      /// @param b this is doc for \p b
+      void foo(int a, int [[^b]]);
+    )cpp",
+       [](HoverInfo &HI) {
+         HI.Name = "b";
+         HI.Kind = index::SymbolKind::Parameter;
+         HI.NamespaceScope = "";
+         HI.LocalScope = "foo::";
+         HI.Type = "int";
+         HI.Definition = "int b";
+         HI.Documentation = "\\brief this is doc for \\p b";
+       }},
+  };
+
+  // Create a tiny index, so tests above can verify documentation is fetched.
+  Symbol IndexSym = func("indexSymbol");
+  IndexSym.Documentation = "comment from index";
+  SymbolSlab::Builder Symbols;
+  Symbols.insert(IndexSym);
+  auto Index =
+      MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
+
+  for (const auto &Case : Cases) {
+    SCOPED_TRACE(Case.Code);
+
+    Annotations T(Case.Code);
+    TestTU TU = TestTU::withCode(T.code());
+    auto AST = TU.build();
+    Config Cfg;
+    Cfg.Hover.ShowAKA = true;
+    WithContextValue WithCfg(Config::Key, std::move(Cfg));
+    auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get());
+    ASSERT_TRUE(H);
+    HoverInfo Expected;
+    Expected.SymRange = T.range();
+    Case.ExpectedBuilder(Expected);
+
+    SCOPED_TRACE(H->present().asPlainText());
+    EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
+    EXPECT_EQ(H->LocalScope, Expected.LocalScope);
+    EXPECT_EQ(H->Name, Expected.Name);
+    EXPECT_EQ(H->Kind, Expected.Kind);
+    EXPECT_EQ(H->Documentation, Expected.Documentation);
+    EXPECT_EQ(H->Definition, Expected.Definition);
+    EXPECT_EQ(H->Type, Expected.Type);
+    EXPECT_EQ(H->ReturnType, Expected.ReturnType);
+    EXPECT_EQ(H->Parameters, Expected.Parameters);
+    EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
+    EXPECT_EQ(H->SymRange, Expected.SymRange);
+    EXPECT_EQ(H->Value, Expected.Value);
+  }
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
index 2d86c91c7ec08..c52d0996893a3 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