tom-anders created this revision.
Herald added subscribers: wenlei, usaxena95, kadircet, arphaman, mgorny.
Herald added a project: All.
tom-anders published this revision for review.
Herald added subscribers: cfe-commits, llvm-commits, MaskRay, ilya-biryukov.
Herald added projects: LLVM, clang-tools-extra.

This adds a new class SymbolDocumentation that does basic doxygen
parsing. Currently, it's only consumed by Hover, but in future
patches we'll probably also want to use it in CodeComplete and
SignatureHelp.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D131853

Files:
  clang-tools-extra/clangd/CMakeLists.txt
  clang-tools-extra/clangd/CodeComplete.cpp
  clang-tools-extra/clangd/CodeCompletionStrings.cpp
  clang-tools-extra/clangd/CodeCompletionStrings.h
  clang-tools-extra/clangd/Hover.cpp
  clang-tools-extra/clangd/Hover.h
  clang-tools-extra/clangd/SymbolDocumentation.cpp
  clang-tools-extra/clangd/SymbolDocumentation.h
  clang-tools-extra/clangd/index/Merge.cpp
  clang-tools-extra/clangd/index/Serialization.cpp
  clang-tools-extra/clangd/index/Symbol.h
  clang-tools-extra/clangd/index/SymbolCollector.cpp
  clang-tools-extra/clangd/index/YAMLSerialization.cpp
  clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp
  clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx
  clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
  clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
  clang-tools-extra/clangd/unittests/HoverTests.cpp
  clang-tools-extra/clangd/unittests/IndexTests.cpp
  clang-tools-extra/clangd/unittests/SerializationTests.cpp
  clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
  clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h
  llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn

Index: llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
===================================================================
--- llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
+++ llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
@@ -117,6 +117,7 @@
     "SemanticHighlighting.cpp",
     "SemanticSelection.cpp",
     "SourceCode.cpp",
+    "SymbolDocumentation.cpp"
     "TUScheduler.cpp",
     "TidyProvider.cpp",
     "URI.cpp",
Index: clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h
@@ -0,0 +1,51 @@
+//===-- SymbolDocumentationMatchers.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
+//
+//===----------------------------------------------------------------------===//
+//
+// GMock matchers for the SymbolDocumentation class
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_MATCHERS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_MATCHERS_H
+#include "SymbolDocumentation.h"
+#include "gmock/gmock.h"
+
+namespace clang {
+namespace clangd {
+
+template <class S>
+testing::Matcher<SymbolDocumentation<S>>
+matchesDoc(const SymbolDocumentation<S> &Expected) {
+  using namespace ::testing;
+
+  std::vector<Matcher<ParameterDocumentation<S>>> ParamMatchers;
+  for (const auto &P : Expected.Parameters)
+    ParamMatchers.push_back(
+        AllOf(Field("Name", &ParameterDocumentation<S>::Name, P.Name),
+              Field("Description", &ParameterDocumentation<S>::Description,
+                    P.Description)));
+
+  return AllOf(
+      Field("Brief", &SymbolDocumentation<S>::Brief, Expected.Brief),
+      Field("Returns", &SymbolDocumentation<S>::Returns, Expected.Returns),
+      Field("Notes", &SymbolDocumentation<S>::Notes,
+            ElementsAreArray(Expected.Notes)),
+      Field("Warnings", &SymbolDocumentation<S>::Warnings,
+            ElementsAreArray(Expected.Warnings)),
+      Field("Parameters", &SymbolDocumentation<S>::Parameters,
+            ElementsAreArray(ParamMatchers)),
+      Field("Description", &SymbolDocumentation<S>::Description,
+            Expected.Description),
+      Field("CommentText", &SymbolDocumentation<S>::CommentText,
+            Expected.CommentText));
+}
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -50,7 +50,7 @@
   return (arg.Name + arg.Signature).str() == Label;
 }
 MATCHER_P(returnType, D, "") { return arg.ReturnType == D; }
-MATCHER_P(doc, D, "") { return arg.Documentation == D; }
+MATCHER_P(doc, D, "") { return arg.Documentation.CommentText == D; }
 MATCHER_P(snippet, S, "") {
   return (arg.Name + arg.CompletionSnippetSuffix).str() == S;
 }
Index: clang-tools-extra/clangd/unittests/SerializationTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/SerializationTests.cpp
+++ clang-tools-extra/clangd/unittests/SerializationTests.cpp
@@ -8,6 +8,7 @@
 
 #include "Headers.h"
 #include "RIFF.h"
+#include "SymbolDocumentationMatchers.h"
 #include "index/Serialization.h"
 #include "support/Logger.h"
 #include "clang/Tooling/CompilationDatabase.h"
@@ -48,7 +49,22 @@
     Line: 1
     Column: 1
 Flags:    129
-Documentation:    'Foo doc'
+Documentation:
+  Brief:       'Foo brief'
+  Returns:     'Foo returns'
+  Description: 'Foo description'
+  Notes:
+    - 'Foo note 1'
+    - 'Foo note 2'
+  Warnings:
+    - 'Foo warning 1'
+    - 'Foo warning 2'
+  Parameters:
+    - Name: 'param1'
+      Description: 'Foo param 1'
+    - Name: 'param2'
+      Description: 'Foo param 2'
+  CommentText: 'Full text would be here'
 ReturnType:    'int'
 IncludeHeaders:
   - Header:    'include1'
@@ -141,7 +157,20 @@
 
   EXPECT_THAT(Sym1, qName("clang::Foo1"));
   EXPECT_EQ(Sym1.Signature, "");
-  EXPECT_EQ(Sym1.Documentation, "Foo doc");
+
+  SymbolDocumentationRef ExpectedDocumentation;
+  ExpectedDocumentation.Brief = "Foo brief";
+  ExpectedDocumentation.Returns = "Foo returns";
+  ExpectedDocumentation.Description = "Foo description";
+  ExpectedDocumentation.Notes = {"Foo note 1", "Foo note 2"};
+  ExpectedDocumentation.Warnings = {"Foo warning 1", "Foo warning 2"};
+  ExpectedDocumentation.Parameters = {
+      {"param1", "Foo param 1"},
+      {"param2", "Foo param 2"},
+  };
+  ExpectedDocumentation.CommentText = "Full text would be here";
+  EXPECT_THAT(Sym1.Documentation, matchesDoc(ExpectedDocumentation));
+
   EXPECT_EQ(Sym1.ReturnType, "int");
   EXPECT_EQ(StringRef(Sym1.CanonicalDeclaration.FileURI), "file:///path/foo.h");
   EXPECT_EQ(Sym1.Origin, SymbolOrigin::Static);
Index: clang-tools-extra/clangd/unittests/IndexTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/IndexTests.cpp
+++ clang-tools-extra/clangd/unittests/IndexTests.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Annotations.h"
+#include "SymbolDocumentationMatchers.h"
 #include "SyncAPI.h"
 #include "TestIndex.h"
 #include "TestTU.h"
@@ -391,7 +392,7 @@
   R.References = 2;
   L.Signature = "()";                   // present in left only
   R.CompletionSnippetSuffix = "{$1:0}"; // present in right only
-  R.Documentation = "--doc--";
+  R.Documentation = SymbolDocumentationRef::descriptionOnly("--doc--");
   L.Origin = SymbolOrigin::Preamble;
   R.Origin = SymbolOrigin::Static;
   R.Type = "expectedType";
@@ -402,7 +403,8 @@
   EXPECT_EQ(M.References, 3u);
   EXPECT_EQ(M.Signature, "()");
   EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}");
-  EXPECT_EQ(M.Documentation, "--doc--");
+  EXPECT_THAT(M.Documentation,
+              matchesDoc(SymbolDocumentationRef::descriptionOnly("--doc--")));
   EXPECT_EQ(M.Type, "expectedType");
   EXPECT_EQ(M.Origin, SymbolOrigin::Preamble | SymbolOrigin::Static |
                           SymbolOrigin::Merge);
@@ -546,16 +548,18 @@
   Symbol L, R;
   L.ID = R.ID = SymbolID("x");
   L.Definition.FileURI = "file:/x.h";
-  R.Documentation = "Forward declarations because x.h is too big to include";
+  R.Documentation = SymbolDocumentationRef::descriptionOnly(
+      "Forward declarations because x.h is too big to include");
   for (auto ClassLikeKind :
        {SymbolKind::Class, SymbolKind::Struct, SymbolKind::Union}) {
     L.SymInfo.Kind = ClassLikeKind;
-    EXPECT_EQ(mergeSymbol(L, R).Documentation, "");
+    ASSERT_TRUE(mergeSymbol(L, R).Documentation.empty());
   }
 
   L.SymInfo.Kind = SymbolKind::Function;
-  R.Documentation = "Documentation from non-class symbols should be included";
-  EXPECT_EQ(mergeSymbol(L, R).Documentation, R.Documentation);
+  R.Documentation = SymbolDocumentationRef::descriptionOnly(
+      "Documentation from non-class symbols should be included");
+  EXPECT_THAT(mergeSymbol(L, R).Documentation, matchesDoc(R.Documentation));
 }
 
 MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") {
Index: clang-tools-extra/clangd/unittests/HoverTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -10,6 +10,7 @@
 #include "Annotations.h"
 #include "Config.h"
 #include "Hover.h"
+#include "SymbolDocumentationMatchers.h"
 #include "TestIndex.h"
 #include "TestTU.h"
 #include "index/MemIndex.h"
@@ -43,7 +44,8 @@
          HI.NamespaceScope = "";
          HI.Name = "foo";
          HI.Kind = index::SymbolKind::Function;
-         HI.Documentation = "Best foo ever.";
+         HI.Documentation =
+             SymbolDocumentationOwned::descriptionOnly("Best foo ever.");
          HI.Definition = "void foo()";
          HI.ReturnType = "void";
          HI.Type = "void ()";
@@ -60,7 +62,8 @@
          HI.NamespaceScope = "ns1::ns2::";
          HI.Name = "foo";
          HI.Kind = index::SymbolKind::Function;
-         HI.Documentation = "Best foo ever.";
+         HI.Documentation =
+             SymbolDocumentationOwned::descriptionOnly("Best foo ever.");
          HI.Definition = "void foo()";
          HI.ReturnType = "void";
          HI.Type = "void ()";
@@ -669,7 +672,8 @@
          HI.Definition = "template <> class Foo<int *>";
          // FIXME: Maybe force instantiation to make use of real template
          // pattern.
-         HI.Documentation = "comment from primary";
+         HI.Documentation =
+             SymbolDocumentationOwned::descriptionOnly("comment from primary");
        }},
       {// Template Type Parameter
        R"cpp(
@@ -721,7 +725,8 @@
          HI.NamespaceScope = "";
          HI.Definition = "float y()";
          HI.LocalScope = "X::";
-         HI.Documentation = "Trivial accessor for `Y`.";
+         HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+             "Trivial accessor for `Y`.");
          HI.Type = "float ()";
          HI.ReturnType = "float";
          HI.Parameters.emplace();
@@ -737,7 +742,8 @@
          HI.NamespaceScope = "";
          HI.Definition = "void setY(float v)";
          HI.LocalScope = "X::";
-         HI.Documentation = "Trivial setter for `Y`.";
+         HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+             "Trivial setter for `Y`.");
          HI.Type = "void (float)";
          HI.ReturnType = "void";
          HI.Parameters.emplace();
@@ -756,7 +762,8 @@
          HI.NamespaceScope = "";
          HI.Definition = "X &setY(float v)";
          HI.LocalScope = "X::";
-         HI.Documentation = "Trivial setter for `Y`.";
+         HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+             "Trivial setter for `Y`.");
          HI.Type = "X &(float)";
          HI.ReturnType = "X &";
          HI.Parameters.emplace();
@@ -776,7 +783,8 @@
          HI.NamespaceScope = "";
          HI.Definition = "void setY(float v)";
          HI.LocalScope = "X::";
-         HI.Documentation = "Trivial setter for `Y`.";
+         HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+             "Trivial setter for `Y`.");
          HI.Type = "void (float)";
          HI.ReturnType = "void";
          HI.Parameters.emplace();
@@ -1068,7 +1076,7 @@
     EXPECT_EQ(H->LocalScope, Expected.LocalScope);
     EXPECT_EQ(H->Name, Expected.Name);
     EXPECT_EQ(H->Kind, Expected.Kind);
-    EXPECT_EQ(H->Documentation, Expected.Documentation);
+    ASSERT_THAT(H->Documentation, matchesDoc(Expected.Documentation));
     EXPECT_EQ(H->Definition, Expected.Definition);
     EXPECT_EQ(H->Type, Expected.Type);
     EXPECT_EQ(H->ReturnType, Expected.ReturnType);
@@ -1345,7 +1353,8 @@
             HI.NamespaceScope = "";
             HI.Type = "void (int)";
             HI.Definition = "void foo(int)";
-            HI.Documentation = "Function definition via pointer";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "Function definition via pointer");
             HI.ReturnType = "void";
             HI.Parameters = {
                 {{"int"}, llvm::None, llvm::None},
@@ -1364,7 +1373,8 @@
             HI.NamespaceScope = "";
             HI.Type = "int (int)";
             HI.Definition = "int foo(int)";
-            HI.Documentation = "Function declaration via call";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "Function declaration via call");
             HI.ReturnType = "int";
             HI.Parameters = {
                 {{"int"}, llvm::None, llvm::None},
@@ -1512,7 +1522,8 @@
             HI.NamespaceScope = "";
             HI.Definition = "typedef int Foo";
             HI.Type = "int";
-            HI.Documentation = "Typedef";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("Typedef");
           }},
       {
           R"cpp(// Typedef with embedded definition
@@ -1527,7 +1538,8 @@
             HI.NamespaceScope = "";
             HI.Definition = "typedef struct Bar Foo";
             HI.Type = "struct Bar";
-            HI.Documentation = "Typedef with embedded definition";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "Typedef with embedded definition");
           }},
       {
           R"cpp(// Namespace
@@ -1603,10 +1615,18 @@
             HI.Kind = index::SymbolKind::Class;
             HI.NamespaceScope = "";
             HI.Definition = "class Foo {}";
-            HI.Documentation = "Forward class declaration";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "Forward class declaration");
           }},
       {
-          R"cpp(// Function declaration
+          R"cpp(
+            /// \brief Function declaration
+            /// \details Some details
+            /// \throws std::runtime_error sometimes
+            /// \param x doc for x
+            /// \warning Watch out!
+            /// \note note1 \note note2
+            /// \return Nothing
             void foo();
             void g() { [[f^oo]](); }
             void foo() {}
@@ -1617,7 +1637,22 @@
             HI.NamespaceScope = "";
             HI.Type = "void ()";
             HI.Definition = "void foo()";
-            HI.Documentation = "Function declaration";
+            HI.Documentation.Brief = "Function declaration";
+            HI.Documentation.Description = "\\details Some details\n\n\\throws "
+                                           "std::runtime_error sometimes";
+            HI.Documentation.Parameters = {
+                {"x", "doc for x"},
+            };
+            HI.Documentation.Returns = "Nothing";
+            HI.Documentation.Notes = {"note1", "note2"};
+            HI.Documentation.Warnings = {"Watch out!"};
+            HI.Documentation.CommentText = R"(\brief Function declaration
+\details Some details
+\throws std::runtime_error sometimes
+\param x doc for x
+\warning Watch out!
+\note note1 \note note2
+\return Nothing)";
             HI.ReturnType = "void";
             HI.Parameters = std::vector<HoverInfo::Param>{};
           }},
@@ -1635,7 +1670,8 @@
             HI.Kind = index::SymbolKind::Enum;
             HI.NamespaceScope = "";
             HI.Definition = "enum Hello {}";
-            HI.Documentation = "Enum declaration";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("Enum declaration");
           }},
       {
           R"cpp(// Enumerator
@@ -1687,7 +1723,8 @@
             HI.NamespaceScope = "";
             HI.Type = "int";
             HI.Definition = "static int hey = 10";
-            HI.Documentation = "Global variable";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("Global variable");
             // FIXME: Value shouldn't be set in this case
             HI.Value = "10 (0xa)";
           }},
@@ -1739,7 +1776,8 @@
             HI.NamespaceScope = "";
             HI.Type = "int ()";
             HI.Definition = "template <> int foo<int>()";
-            HI.Documentation = "Templated function";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("Templated function");
             HI.ReturnType = "int";
             HI.Parameters = std::vector<HoverInfo::Param>{};
             // FIXME: We should populate template parameters with arguments in
@@ -1776,7 +1814,8 @@
             HI.Definition = "void indexSymbol()";
             HI.ReturnType = "void";
             HI.Parameters = std::vector<HoverInfo::Param>{};
-            HI.Documentation = "comment from index";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("comment from index");
           }},
       {
           R"cpp(// Simple initialization with auto
@@ -1945,7 +1984,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "auto function return with trailing type";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "auto function return with trailing type");
           }},
       {
           R"cpp(// trailing return type
@@ -1958,7 +1998,8 @@
             HI.Name = "decltype";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "trailing return type";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "trailing return type");
           }},
       {
           R"cpp(// auto in function return
@@ -1971,7 +2012,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "auto in function return";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "auto in function return");
           }},
       {
           R"cpp(// auto& in function return
@@ -1985,7 +2027,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "auto& in function return";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "auto& in function return");
           }},
       {
           R"cpp(// auto* in function return
@@ -1999,7 +2042,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "auto* in function return";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "auto* in function return");
           }},
       {
           R"cpp(// const auto& in function return
@@ -2013,7 +2057,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "const auto& in function return";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "const auto& in function return");
           }},
       {
           R"cpp(// decltype(auto) in function return
@@ -2026,7 +2071,8 @@
             HI.Name = "decltype";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation = "decltype(auto) in function return";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "decltype(auto) in function return");
           }},
       {
           R"cpp(// decltype(auto) reference in function return
@@ -2116,8 +2162,8 @@
             HI.Name = "decltype";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "Bar";
-            HI.Documentation =
-                "decltype of function with trailing return type.";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "decltype of function with trailing return type.");
           }},
       {
           R"cpp(// decltype of var with decltype.
@@ -2187,7 +2233,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "cls_type // aka: cls";
-            HI.Documentation = "auto on alias";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("auto on alias");
           }},
       {
           R"cpp(// auto on alias
@@ -2199,7 +2246,8 @@
             HI.Name = "auto";
             HI.Kind = index::SymbolKind::TypeAlias;
             HI.Definition = "templ<int>";
-            HI.Documentation = "auto on alias";
+            HI.Documentation =
+                SymbolDocumentationOwned::descriptionOnly("auto on alias");
           }},
       {
           R"cpp(// Undeduced auto declaration
@@ -2290,7 +2338,8 @@
             HI.Kind = index::SymbolKind::Struct;
             HI.NamespaceScope = "";
             HI.Name = "cls<cls<cls<int>>>";
-            HI.Documentation = "type of nested templates.";
+            HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+                "type of nested templates.");
           }},
       {
           R"cpp(// type with decltype
@@ -2584,13 +2633,15 @@
          HI.Name = "nonnull";
          HI.Kind = index::SymbolKind::Unknown; // FIXME: no suitable value
          HI.Definition = "__attribute__((nonnull))";
-         HI.Documentation = Attr::getDocumentation(attr::NonNull).str();
+         HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+             Attr::getDocumentation(attr::NonNull).str());
        }},
   };
 
   // Create a tiny index, so tests above can verify documentation is fetched.
   Symbol IndexSym = func("indexSymbol");
-  IndexSym.Documentation = "comment from index";
+  IndexSym.Documentation =
+      SymbolDocumentationRef::descriptionOnly("comment from index");
   SymbolSlab::Builder Symbols;
   Symbols.insert(IndexSym);
   auto Index =
@@ -2623,7 +2674,7 @@
     EXPECT_EQ(H->LocalScope, Expected.LocalScope);
     EXPECT_EQ(H->Name, Expected.Name);
     EXPECT_EQ(H->Kind, Expected.Kind);
-    EXPECT_EQ(H->Documentation, Expected.Documentation);
+    ASSERT_THAT(H->Documentation, matchesDoc(Expected.Documentation));
     EXPECT_EQ(H->Definition, Expected.Definition);
     EXPECT_EQ(H->Type, Expected.Type);
     EXPECT_EQ(H->ReturnType, Expected.ReturnType);
@@ -2647,7 +2698,8 @@
   auto AST = TU.build();
   Symbol IndexSym;
   IndexSym.ID = getSymbolID(&findDecl(AST, "X"));
-  IndexSym.Documentation = "comment from index";
+  IndexSym.Documentation =
+      SymbolDocumentationRef::descriptionOnly("comment from index");
   SymbolSlab::Builder Symbols;
   Symbols.insert(IndexSym);
   auto Index =
@@ -2656,7 +2708,7 @@
   for (const auto &P : T.points()) {
     auto H = getHover(AST, P, format::getLLVMStyle(), Index.get());
     ASSERT_TRUE(H);
-    EXPECT_EQ(H->Documentation, IndexSym.Documentation);
+    ASSERT_THAT(H->Documentation.toRef(), matchesDoc(IndexSym.Documentation));
   }
 }
 
@@ -2681,7 +2733,8 @@
   for (const auto &P : T.points()) {
     auto H = getHover(AST, P, format::getLLVMStyle(), nullptr);
     ASSERT_TRUE(H);
-    EXPECT_EQ(H->Documentation, "doc");
+    ASSERT_THAT(H->Documentation,
+                matchesDoc(SymbolDocumentationOwned::descriptionOnly("doc")));
   }
 }
 
@@ -2719,7 +2772,9 @@
     for (const auto &P : T.points(Comment)) {
       auto H = getHover(AST, P, format::getLLVMStyle(), nullptr);
       ASSERT_TRUE(H);
-      EXPECT_EQ(H->Documentation, Comment);
+      ASSERT_THAT(
+          H->Documentation,
+          matchesDoc(SymbolDocumentationOwned::descriptionOnly(Comment)));
     }
   }
 }
@@ -2751,7 +2806,14 @@
                 {{"typename"}, std::string("T"), llvm::None},
                 {{"typename"}, std::string("C"), std::string("bool")},
             };
-            HI.Documentation = "documentation";
+            HI.Documentation.Brief = "brief";
+            HI.Documentation.Description = "details";
+            HI.Documentation.Parameters = {
+                {"Parameters", "should be ignored for classes"}};
+            HI.Documentation.Returns = "Returns should be ignored for classes";
+            HI.Documentation.Notes = {"note1", "note2"};
+            HI.Documentation.Warnings = {"warning1", "warning2"};
+            HI.Documentation.CommentText = "Not used for Hover presentation";
             HI.Definition =
                 "template <typename T, typename C = bool> class Foo {}";
             HI.Name = "foo";
@@ -2759,8 +2821,17 @@
           },
           R"(class foo
 
+brief
 Size: 10 bytes
-documentation
+details
+
+Warnings:
+- warning1
+- warning2
+
+Notes:
+- note1
+- note2
 
 template <typename T, typename C = bool> class Foo {})",
       },
@@ -2779,17 +2850,37 @@
             HI.Parameters->push_back(P);
             P.Default = "default";
             HI.Parameters->push_back(P);
+            HI.Documentation.Brief = "brief";
+            HI.Documentation.Description = "details";
+            HI.Documentation.Parameters = {
+                {"foo", "param doc"},
+                {"bar", "doc for parameter not in the signature"}};
+            HI.Documentation.Returns = "doc for return";
+            HI.Documentation.Notes = {"note1", "note2"};
+            HI.Documentation.Warnings = {"warning1", "warning2"};
+            HI.Documentation.CommentText = "Not used for Hover presentation";
             HI.NamespaceScope = "ns::";
             HI.Definition = "ret_type foo(params) {}";
           },
           "function foo\n"
           "\n"
-          "→ ret_type (aka can_ret_type)\n"
+          "brief\n"
+          "→ ret_type (aka can_ret_type): doc for return\n"
           "Parameters:\n"
           "- \n"
           "- type (aka can_type)\n"
-          "- type foo (aka can_type)\n"
+          "- type foo (aka can_type): param doc\n"
           "- type foo = default (aka can_type)\n"
+          "- bar: doc for parameter not in the signature\n"
+          "details\n"
+          "\n"
+          "Warnings:\n"
+          "- warning1\n"
+          "- warning2\n"
+          "\n"
+          "Notes:\n"
+          "- note1\n"
+          "- note2\n"
           "\n"
           "// In namespace ns\n"
           "ret_type foo(params) {}",
@@ -3146,7 +3237,7 @@
 
   template <typename T>
   struct S {
-    // Foo bar baz
+    /// Foo bar baz
     friend auto operator<=>(S, S) = default;
   };
   static_assert(S<void>() =^= S<void>());
@@ -3156,7 +3247,10 @@
   TU.ExtraArgs.push_back("-std=c++20");
   auto AST = TU.build();
   auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
-  EXPECT_EQ(HI->Documentation, "Foo bar baz");
+
+  ASSERT_THAT(
+      HI->Documentation,
+      matchesDoc(SymbolDocumentationOwned::descriptionOnly("Foo bar baz")));
 }
 
 TEST(Hover, ForwardStructNoCrash) {
Index: clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
+++ clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CodeCompletionStrings.h"
+#include "SymbolDocumentationMatchers.h"
 #include "TestTU.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
 #include "gmock/gmock.h"
@@ -57,12 +58,121 @@
             "Annotation: Ano\n\nIs this brief?");
 }
 
-TEST_F(CompletionStringTest, GetDeclCommentBadUTF8) {
+TEST_F(CompletionStringTest, GetDeclDocumentationBadUTF8) {
   // <ff> is not a valid byte here, should be replaced by encoded <U+FFFD>.
-  auto TU = TestTU::withCode("/*x\xffy*/ struct X;");
+  const std::string Code = llvm::formatv(R"cpp(
+  /// \brief {0}
+  /// \details {0}
+  /// \param {0} {0}
+  /// \warning {0}
+  /// \note {0}
+  /// \return {0}
+  struct X;
+  )cpp",
+                                         "x\xffy");
+
+  auto TU = TestTU::withCode(Code);
   auto AST = TU.build();
-  EXPECT_EQ("x\xef\xbf\xbdy",
-            getDeclComment(AST.getASTContext(), findDecl(AST, "X")));
+
+  const std::string Utf8Replacement = "x\xef\xbf\xbdy";
+  SymbolDocumentationOwned ExpectedDoc;
+  ExpectedDoc.Brief = Utf8Replacement;
+  ExpectedDoc.Returns = Utf8Replacement;
+  ExpectedDoc.Parameters = {{Utf8Replacement, Utf8Replacement}};
+  ExpectedDoc.Notes = {Utf8Replacement};
+  ExpectedDoc.Warnings = {Utf8Replacement};
+  ExpectedDoc.Description = {"\\details " + Utf8Replacement};
+  ExpectedDoc.CommentText = llvm::formatv(R"(\brief {0}
+\details {0}
+\param {0} {0}
+\warning {0}
+\note {0}
+\return {0})", Utf8Replacement);
+
+  EXPECT_THAT(getDeclDocumentation(AST.getASTContext(), findDecl(AST, "X")),
+              matchesDoc(ExpectedDoc));
+}
+
+TEST_F(CompletionStringTest, DoxygenParsing) {
+  struct {
+    const char *const Code;
+    const std::function<void(SymbolDocumentationOwned &)> ExpectedBuilder;
+  } Cases[] = {
+      {R"cpp(
+    // Hello world
+    void foo();
+    )cpp",
+       [](SymbolDocumentationOwned &Doc) { Doc.Description = "Hello world"; }},
+      {R"cpp(
+    /*! 
+     * \brief brief
+     * \details details
+     */
+    void foo();
+    )cpp",
+       [](SymbolDocumentationOwned &Doc) {
+         Doc.Brief = "brief";
+         Doc.Description = "\\details details";
+       }},
+      {R"cpp(
+    /** 
+     * @brief brief
+     * @details details
+     * @see somewhere else
+     */
+    void foo();
+    )cpp",
+       [](SymbolDocumentationOwned &Doc) {
+         Doc.Brief = "brief";
+         Doc.Description = "@details details\n\n@see somewhere else";
+       }},
+      {R"cpp(
+    /*! 
+     * @brief brief
+     * @details details
+     * @param foo foodoc
+     * @throws ball at hoop
+     * @note note1
+     * @warning warning1
+     * @note note2
+     * @warning warning2
+     * @param bar bardoc
+     * @return something
+     */
+    void foo();
+    )cpp",
+       [](SymbolDocumentationOwned &Doc) {
+         Doc.Brief = "brief";
+         Doc.Description = "@details details\n\n@throws ball at hoop";
+         Doc.Parameters = {{"foo", "foodoc"}, {"bar", "bardoc"}};
+         Doc.Warnings = {"warning1", "warning2"};
+         Doc.Notes = {"note1", "note2"};
+         Doc.Returns = "something";
+       }},
+      {R"cpp(
+    /// @brief Here's \b bold \e italic and \p code
+    int foo;
+    )cpp",
+       [](SymbolDocumentationOwned &Doc) {
+         Doc.Brief = "Here's **bold** *italic* and `code`";
+       }}};
+
+  for (const auto &Case : Cases) {
+    SCOPED_TRACE(Case.Code);
+
+    auto TU = TestTU::withCode(Case.Code);
+    auto AST = TU.build();
+    auto &Ctx = AST.getASTContext();
+    const auto &Decl = findDecl(AST, "foo");
+
+    SymbolDocumentationOwned ExpectedDoc;
+    ExpectedDoc.CommentText =
+        getCompletionComment(Ctx, &Decl)
+            ->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+    Case.ExpectedBuilder(ExpectedDoc);
+
+    EXPECT_THAT(getDeclDocumentation(Ctx, Decl), matchesDoc(ExpectedDoc));
+  }
 }
 
 TEST_F(CompletionStringTest, MultipleAnnotations) {
Index: clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -2330,9 +2330,9 @@
 
 TEST(SignatureHelpTest, IndexDocumentation) {
   Symbol Foo0 = sym("foo", index::SymbolKind::Function, "@F@\\0#");
-  Foo0.Documentation = "doc from the index";
+  Foo0.Documentation.CommentText = "doc from the index";
   Symbol Foo1 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#");
-  Foo1.Documentation = "doc from the index";
+  Foo1.Documentation.CommentText = "doc from the index";
   Symbol Foo2 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#I#");
 
   StringRef Sig0 = R"cpp(
Index: clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp
===================================================================
--- clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp
+++ clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp
@@ -3,6 +3,11 @@
 
 // This introduces a symbol, a reference and a relation.
 struct Bar : public Foo {
-  // This introduces an OverriddenBy relation by implementing Foo::Func.
+  /// \brief This introduces an OverriddenBy relation by implementing Foo::Func.
+  /// \details And it also introduces some doxygen!
+  /// \param foo bar
+  /// \warning !!!
+  /// \note a note
+  /// \return nothing
   void Func() override {}
 };
Index: clang-tools-extra/clangd/index/YAMLSerialization.cpp
===================================================================
--- clang-tools-extra/clangd/index/YAMLSerialization.cpp
+++ clang-tools-extra/clangd/index/YAMLSerialization.cpp
@@ -30,6 +30,7 @@
 
 LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Symbol::IncludeHeaderWithReferences)
 LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Ref)
+LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::ParameterDocumentationRef)
 
 namespace {
 using RefBundle =
@@ -59,11 +60,13 @@
 using clang::clangd::FileDigest;
 using clang::clangd::IncludeGraph;
 using clang::clangd::IncludeGraphNode;
+using clang::clangd::ParameterDocumentationRef;
 using clang::clangd::Ref;
 using clang::clangd::RefKind;
 using clang::clangd::Relation;
 using clang::clangd::RelationKind;
 using clang::clangd::Symbol;
+using clang::clangd::SymbolDocumentationRef;
 using clang::clangd::SymbolID;
 using clang::clangd::SymbolLocation;
 using clang::index::SymbolInfo;
@@ -174,6 +177,28 @@
   }
 };
 
+template <> struct MappingTraits<ParameterDocumentationRef> {
+  static void mapping(IO &IO, ParameterDocumentationRef &P) {
+    IO.mapRequired("Name", P.Name);
+    IO.mapRequired("Description", P.Description);
+  }
+};
+
+template <> struct MappingTraits<SymbolDocumentationRef> {
+  static void mapping(IO &IO, SymbolDocumentationRef &Doc) {
+    IO.mapOptional("Brief", Doc.Brief);
+    IO.mapOptional("Returns", Doc.Returns);
+
+    IO.mapOptional("Notes", Doc.Notes);
+    IO.mapOptional("Warnings", Doc.Warnings);
+
+    IO.mapOptional("Parameters", Doc.Parameters);
+
+    IO.mapOptional("Description", Doc.Description);
+    IO.mapOptional("CommentText", Doc.CommentText);
+  }
+};
+
 template <> struct MappingTraits<Symbol> {
   static void mapping(IO &IO, Symbol &Sym) {
     MappingNormalization<NormalizedSymbolID, SymbolID> NSymbolID(IO, Sym.ID);
Index: clang-tools-extra/clangd/index/SymbolCollector.cpp
===================================================================
--- clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -912,16 +912,17 @@
       *ASTCtx, *PP, CodeCompletionContext::CCC_Symbol, *CompletionAllocator,
       *CompletionTUInfo,
       /*IncludeBriefComments*/ false);
-  std::string Documentation =
-      formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion,
-                                              /*CommentsFromHeaders=*/true));
+
+  const SymbolDocumentationOwned Documentation =
+      getDocumentation(Ctx, SymbolCompletion, /*CommentsFromHeaders=*/true);
+
   if (!(S.Flags & Symbol::IndexedForCodeCompletion)) {
     if (Opts.StoreAllDocumentation)
-      S.Documentation = Documentation;
+      S.Documentation = Documentation.toRef();
     Symbols.insert(S);
     return Symbols.find(S.ID);
   }
-  S.Documentation = Documentation;
+  S.Documentation = Documentation.toRef();
   std::string Signature;
   std::string SnippetSuffix;
   getSignature(*CCS, &Signature, &SnippetSuffix);
Index: clang-tools-extra/clangd/index/Symbol.h
===================================================================
--- clang-tools-extra/clangd/index/Symbol.h
+++ clang-tools-extra/clangd/index/Symbol.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_H
 
+#include "SymbolDocumentation.h"
 #include "index/SymbolID.h"
 #include "index/SymbolLocation.h"
 #include "index/SymbolOrigin.h"
@@ -73,7 +74,7 @@
   /// Only set when the symbol is indexed for completion.
   llvm::StringRef CompletionSnippetSuffix;
   /// Documentation including comment for the symbol declaration.
-  llvm::StringRef Documentation;
+  SymbolDocumentationRef Documentation;
   /// Type when this symbol is used in an expression. (Short display form).
   /// e.g. return type of a function, or type of a variable.
   /// Only set when the symbol is indexed for completion.
@@ -150,7 +151,20 @@
   CB(S.TemplateSpecializationArgs);
   CB(S.Signature);
   CB(S.CompletionSnippetSuffix);
-  CB(S.Documentation);
+
+  CB(S.Documentation.Brief);
+  CB(S.Documentation.Returns);
+  for (auto &Note : S.Documentation.Notes)
+    CB(Note);
+  for (auto &Warning : S.Documentation.Warnings)
+    CB(Warning);
+  for (auto &ParamDoc : S.Documentation.Parameters) {
+    CB(ParamDoc.Name);
+    CB(ParamDoc.Description);
+  }
+  CB(S.Documentation.Description);
+  CB(S.Documentation.CommentText);
+
   CB(S.ReturnType);
   CB(S.Type);
   auto RawCharPointerCB = [&CB](const char *&P) {
Index: clang-tools-extra/clangd/index/Serialization.cpp
===================================================================
--- clang-tools-extra/clangd/index/Serialization.cpp
+++ clang-tools-extra/clangd/index/Serialization.cpp
@@ -283,6 +283,57 @@
   return Loc;
 }
 
+void writeSymbolDocumentation(const SymbolDocumentationRef &Doc,
+                              const StringTableOut &Strings,
+                              llvm::raw_ostream &OS) {
+  writeVar(Strings.index(Doc.Brief), OS);
+  writeVar(Strings.index(Doc.Returns), OS);
+
+  writeVar(Doc.Notes.size(), OS);
+  for (const auto &Note : Doc.Notes)
+    writeVar(Strings.index(Note), OS);
+
+  writeVar(Doc.Warnings.size(), OS);
+  for (const auto &Warning : Doc.Warnings)
+    writeVar(Strings.index(Warning), OS);
+
+  writeVar(Doc.Parameters.size(), OS);
+  for (const auto &ParamDoc : Doc.Parameters) {
+    writeVar(Strings.index(ParamDoc.Name), OS);
+    writeVar(Strings.index(ParamDoc.Description), OS);
+  }
+
+  writeVar(Strings.index(Doc.Description), OS);
+  writeVar(Strings.index(Doc.CommentText), OS);
+}
+
+SymbolDocumentationRef
+readSymbolDocumentation(Reader &Data, llvm::ArrayRef<llvm::StringRef> Strings) {
+  SymbolDocumentationRef Doc;
+  Doc.Brief = Data.consumeString(Strings);
+  Doc.Returns = Data.consumeString(Strings);
+
+  if (!Data.consumeSize(Doc.Notes))
+    return Doc;
+  for (auto &Note : Doc.Notes)
+    Note = Data.consumeString(Strings);
+
+  if (!Data.consumeSize(Doc.Warnings))
+    return Doc;
+  for (auto &Warning : Doc.Warnings)
+    Warning = Data.consumeString(Strings);
+
+  if (!Data.consumeSize(Doc.Parameters))
+    return Doc;
+  for (auto &ParamDoc : Doc.Parameters)
+    ParamDoc = {Data.consumeString(Strings), Data.consumeString(Strings)};
+
+  Doc.Description = Data.consumeString(Strings);
+  Doc.CommentText = Data.consumeString(Strings);
+
+  return Doc;
+}
+
 IncludeGraphNode readIncludeGraphNode(Reader &Data,
                                       llvm::ArrayRef<llvm::StringRef> Strings) {
   IncludeGraphNode IGN;
@@ -325,7 +376,7 @@
   OS.write(static_cast<uint8_t>(Sym.Flags));
   writeVar(Strings.index(Sym.Signature), OS);
   writeVar(Strings.index(Sym.CompletionSnippetSuffix), OS);
-  writeVar(Strings.index(Sym.Documentation), OS);
+  writeSymbolDocumentation(Sym.Documentation, Strings, OS);
   writeVar(Strings.index(Sym.ReturnType), OS);
   writeVar(Strings.index(Sym.Type), OS);
 
@@ -354,7 +405,7 @@
   Sym.Origin = Origin;
   Sym.Signature = Data.consumeString(Strings);
   Sym.CompletionSnippetSuffix = Data.consumeString(Strings);
-  Sym.Documentation = Data.consumeString(Strings);
+  Sym.Documentation = readSymbolDocumentation(Data, Strings);
   Sym.ReturnType = Data.consumeString(Strings);
   Sym.Type = Data.consumeString(Strings);
   if (!Data.consumeSize(Sym.IncludeHeaders))
@@ -455,7 +506,7 @@
 // The current versioning scheme is simple - non-current versions are rejected.
 // If you make a breaking change, bump this version number to invalidate stored
 // data. Later we may want to support some backward compatibility.
-constexpr static uint32_t Version = 17;
+constexpr static uint32_t Version = 18;
 
 llvm::Expected<IndexFileIn> readRIFF(llvm::StringRef Data,
                                      SymbolOrigin Origin) {
Index: clang-tools-extra/clangd/index/Merge.cpp
===================================================================
--- clang-tools-extra/clangd/index/Merge.cpp
+++ clang-tools-extra/clangd/index/Merge.cpp
@@ -227,7 +227,7 @@
     S.Signature = O.Signature;
   if (S.CompletionSnippetSuffix == "")
     S.CompletionSnippetSuffix = O.CompletionSnippetSuffix;
-  if (S.Documentation == "") {
+  if (S.Documentation.empty()) {
     // Don't accept documentation from bare forward class declarations, if there
     // is a definition and it didn't provide one. S is often an undocumented
     // class, and O is a non-canonical forward decl preceded by an irrelevant
Index: clang-tools-extra/clangd/SymbolDocumentation.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/SymbolDocumentation.h
@@ -0,0 +1,101 @@
+//===--- 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 "clang/AST/ASTContext.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentVisitor.h"
+
+namespace clang {
+namespace clangd {
+
+template <class String> struct ParameterDocumentation {
+  String Name;
+  String Description;
+
+  ParameterDocumentation<llvm::StringRef> toRef() const;
+  ParameterDocumentation<std::string> toOwned() const;
+};
+
+using ParameterDocumentationRef = ParameterDocumentation<llvm::StringRef>;
+using ParameterDocumentationOwned = ParameterDocumentation<std::string>;
+
+/// @brief Represents a parsed doxygen comment.
+/// @details Currently there's special handling for the "brief", "param"
+/// "returns", "note" and "warning" commands. The content of all other
+/// paragraphs will be appended to the #Description field.
+/// If you're only interested in the full comment, but with comment
+/// markers stripped, use the #CommentText field.
+/// \tparam String When built from a declaration, we're building the strings
+/// by ourselves, so in this case String==std::string.
+/// However, when storing the contents of this class in the index, we need to
+/// use llvm::StringRef. To connvert between std::string and llvm::StringRef
+/// versions of this class, use toRef() and toOwned().
+template <class String> class SymbolDocumentation {
+public:
+  friend class CommentToSymbolDocumentation;
+
+  static SymbolDocumentation<String> descriptionOnly(String &&Description) {
+    SymbolDocumentation<String> Doc;
+    Doc.Description = Description;
+    Doc.CommentText = Description;
+    return Doc;
+  }
+
+  /// Constructs with all fields as empty strings/vectors.
+  SymbolDocumentation() = default;
+
+  SymbolDocumentation<llvm::StringRef> toRef() const;
+  SymbolDocumentation<std::string> toOwned() const;
+
+  bool empty() const { return CommentText.empty(); }
+
+  /// Paragraph of the "brief" command.
+  String Brief;
+
+  /// Paragraph of the "return" command.
+  String Returns;
+
+  /// Paragraph(s) of the "note" command(s)
+  llvm::SmallVector<String, 1> Notes;
+  /// Paragraph(s) of the "warning" command(s)
+  llvm::SmallVector<String, 1> Warnings;
+
+  /// Parsed paragaph(s) of the "param" comamnd(s)
+  llvm::SmallVector<ParameterDocumentation<String>> Parameters;
+
+  /// All the paragraphs we don't have any special handling for,
+  /// e.g. "details".
+  String Description;
+
+  /// The full documentation comment with comment markers stripped.
+  /// See clang::RawComment::getFormattedText() for the detailed
+  /// explanation of how the comment text is transformed.
+  String CommentText;
+};
+
+using SymbolDocumentationOwned = SymbolDocumentation<std::string>;
+using SymbolDocumentationRef = SymbolDocumentation<llvm::StringRef>;
+
+/// @param RC the comment to parse
+/// @param D the declaration that \p RC belongs to
+/// @return parsed doxgen documentation.
+SymbolDocumentationOwned
+parseDoxygenComment(const RawComment &RC, const ASTContext &Ctx, const Decl *D);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
Index: clang-tools-extra/clangd/SymbolDocumentation.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -0,0 +1,205 @@
+//===--- 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 "clang/AST/CommentVisitor.h"
+#include "llvm/Support/JSON.h"
+
+namespace clang {
+namespace clangd {
+
+void ensureUTF8(std::string &Str) {
+  if (!llvm::json::isUTF8(Str))
+    Str = llvm::json::fixUTF8(Str);
+}
+
+class BlockCommentToString
+    : public comments::ConstCommentVisitor<BlockCommentToString> {
+public:
+  BlockCommentToString(std::string &Out, const ASTContext &Ctx)
+      : Out(Out), Ctx(Ctx) {}
+
+  void visitParagraphComment(const comments::ParagraphComment *C) {
+    for (const auto *Child = C->child_begin(); Child != C->child_end();
+         ++Child) {
+      visit(*Child);
+    }
+  }
+
+  void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+    Out << (B->getCommandMarker() == (comments::CommandMarkerKind::CMK_At)
+                ? '@'
+                : '\\')
+        << B->getCommandName(Ctx.getCommentCommandTraits());
+
+    visit(B->getParagraph());
+  }
+
+  void visitTextComment(const comments::TextComment *C) {
+    // If this is the very first node, the paragraph has no doxygen command,
+    // so there will be a leading space -> Trim it
+    // Otherwise just trim trailing space
+    if (Out.str().empty())
+      Out << C->getText().trim();
+    else
+      Out << C->getText().rtrim();
+  }
+
+  void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+    const std::string SurroundWith = [C] {
+      switch (C->getRenderKind()) {
+      case comments::InlineCommandComment::RenderKind::RenderMonospaced:
+        return "`";
+      case comments::InlineCommandComment::RenderKind::RenderBold:
+        return "**";
+      case comments::InlineCommandComment::RenderKind::RenderEmphasized:
+        return "*";
+      default:
+        return "";
+      }
+    }();
+
+    Out << " " << SurroundWith;
+    for (unsigned I = 0; I < C->getNumArgs(); ++I) {
+      Out << C->getArgText(I);
+    }
+    Out << SurroundWith;
+  }
+
+private:
+  llvm::raw_string_ostream Out;
+  const ASTContext &Ctx;
+};
+
+class CommentToSymbolDocumentation
+    : public comments::ConstCommentVisitor<CommentToSymbolDocumentation> {
+public:
+  CommentToSymbolDocumentation(const RawComment &RC, const ASTContext &Ctx,
+                               const Decl *D, SymbolDocumentationOwned &Doc)
+      : FullComment(RC.parse(Ctx, nullptr, D)), Output(Doc), Ctx(Ctx) {
+
+    Doc.CommentText =
+        RC.getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+
+    for (auto *Block : FullComment->getBlocks()) {
+      visit(Block);
+    }
+  }
+
+  void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+    const llvm::StringRef CommandName =
+        B->getCommandName(Ctx.getCommentCommandTraits());
+
+    // 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 (CommandName == "brief") {
+      BlockCommentToString(Output.Brief, Ctx).visit(B->getParagraph());
+    } else if (CommandName == "return") {
+      BlockCommentToString(Output.Returns, Ctx).visit(B->getParagraph());
+    } else if (CommandName == "warning") {
+      BlockCommentToString(Output.Warnings.emplace_back(), Ctx)
+          .visit(B->getParagraph());
+    } else if (CommandName == "note") {
+      BlockCommentToString(Output.Notes.emplace_back(), Ctx)
+          .visit(B->getParagraph());
+    } else {
+      if (!Output.Description.empty())
+        Output.Description += "\n\n";
+
+      BlockCommentToString(Output.Description, Ctx).visit(B);
+    }
+  }
+
+  void visitParagraphComment(const comments::ParagraphComment *P) {
+    BlockCommentToString(Output.Description, Ctx).visit(P);
+  }
+
+  void visitParamCommandComment(const comments::ParamCommandComment *P) {
+    if (P->hasParamName() && P->hasNonWhitespaceParagraph()) {
+      ParameterDocumentationOwned Doc;
+      Doc.Name = P->getParamNameAsWritten().str();
+      BlockCommentToString(Doc.Description, Ctx).visit(P->getParagraph());
+      Output.Parameters.push_back(std::move(Doc));
+    }
+  }
+
+private:
+  comments::FullComment *FullComment;
+  SymbolDocumentationOwned &Output;
+  const ASTContext &Ctx;
+};
+
+SymbolDocumentationOwned parseDoxygenComment(const RawComment &RC,
+                                             const ASTContext &Ctx,
+                                             const Decl *D) {
+  SymbolDocumentationOwned Doc;
+  CommentToSymbolDocumentation(RC, Ctx, D, Doc);
+
+  // Clang requires source to be UTF-8, but doesn't enforce this in comments.
+  ensureUTF8(Doc.Brief);
+  ensureUTF8(Doc.Returns);
+  for (auto &Note : Doc.Notes)
+    ensureUTF8(Note);
+  for (auto &Warning : Doc.Warnings)
+    ensureUTF8(Warning);
+  for (auto &Param : Doc.Parameters) {
+    ensureUTF8(Param.Name);
+    ensureUTF8(Param.Description);
+  }
+  ensureUTF8(Doc.Description);
+  ensureUTF8(Doc.CommentText);
+
+  return Doc;
+}
+
+template struct ParameterDocumentation<std::string>;
+template struct ParameterDocumentation<llvm::StringRef>;
+
+template <class S1, class S2>
+SymbolDocumentation<S1> convert(const SymbolDocumentation<S2> &In) {
+  SymbolDocumentation<S1> Doc;
+
+  Doc.Brief = In.Brief;
+  Doc.Returns = In.Returns;
+
+  Doc.Notes.reserve(In.Notes.size());
+  for (const auto &Note : In.Notes) {
+    Doc.Notes.emplace_back(Note);
+  }
+
+  Doc.Warnings.reserve(In.Warnings.size());
+  for (const auto &Warning : In.Warnings) {
+    Doc.Warnings.emplace_back(Warning);
+  }
+
+  Doc.Parameters.reserve(In.Parameters.size());
+  for (const auto &ParamDoc : In.Parameters) {
+    Doc.Parameters.emplace_back(ParameterDocumentation<S1>{
+        S1(ParamDoc.Name), S1(ParamDoc.Description)});
+  }
+
+  Doc.Description = In.Description;
+  Doc.CommentText = In.CommentText;
+
+  return Doc;
+}
+
+template <> SymbolDocumentationRef SymbolDocumentationOwned::toRef() const {
+  return convert<llvm::StringRef>(*this);
+}
+
+template <> SymbolDocumentationOwned SymbolDocumentationRef::toOwned() const {
+  return convert<std::string>(*this);
+}
+
+template class SymbolDocumentation<std::string>;
+template class SymbolDocumentation<llvm::StringRef>;
+
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/Hover.h
===================================================================
--- clang-tools-extra/clangd/Hover.h
+++ 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"
 
@@ -68,7 +69,7 @@
   std::string Name;
   llvm::Optional<Range> SymRange;
   index::SymbolKind Kind = index::SymbolKind::Unknown;
-  std::string Documentation;
+  SymbolDocumentationOwned Documentation;
   /// Source code containing the definition of the symbol.
   std::string Definition;
   const char *DefinitionLanguage = "cpp";
Index: clang-tools-extra/clangd/Hover.cpp
===================================================================
--- clang-tools-extra/clangd/Hover.cpp
+++ clang-tools-extra/clangd/Hover.cpp
@@ -332,7 +332,7 @@
   LookupRequest Req;
   Req.IDs.insert(ID);
   Index->lookup(Req, [&](const Symbol &S) {
-    Hover.Documentation = std::string(S.Documentation);
+    Hover.Documentation = S.Documentation.toOwned();
   });
 }
 
@@ -590,10 +590,11 @@
 
   HI.Name = printName(Ctx, *D);
   const auto *CommentD = getDeclForComment(D);
-  HI.Documentation = getDeclComment(Ctx, *CommentD);
+  HI.Documentation = getDeclDocumentation(Ctx, *CommentD);
   enhanceFromIndex(HI, *CommentD, Index);
   if (HI.Documentation.empty())
-    HI.Documentation = synthesizeDocumentation(D);
+    HI.Documentation =
+        SymbolDocumentationOwned::descriptionOnly(synthesizeDocumentation(D));
 
   HI.Kind = index::getSymbolInfo(D).Kind;
 
@@ -720,7 +721,7 @@
 
     if (const auto *D = QT->getAsTagDecl()) {
       const auto *CommentD = getDeclForComment(D);
-      HI.Documentation = getDeclComment(ASTCtx, *CommentD);
+      HI.Documentation = getDeclDocumentation(ASTCtx, *CommentD);
       enhanceFromIndex(HI, *CommentD, Index);
     }
   }
@@ -785,7 +786,8 @@
     llvm::raw_string_ostream OS(HI.Definition);
     A->printPretty(OS, AST.getASTContext().getPrintingPolicy());
   }
-  HI.Documentation = Attr::getDocumentation(A->getKind()).str();
+  HI.Documentation = SymbolDocumentationOwned::descriptionOnly(
+      Attr::getDocumentation(A->getKind()).str());
   return HI;
 }
 
@@ -1086,6 +1088,10 @@
 
   // Put a linebreak after header to increase readability.
   Output.addRuler();
+
+  if (!Documentation.Brief.empty())
+    parseDocumentation(Documentation.Brief, Output);
+
   // Print Types on their own lines to reduce chances of getting line-wrapped by
   // editor, as they might be long.
   if (ReturnType) {
@@ -1094,15 +1100,44 @@
     // Parameters:
     // - `bool param1`
     // - `int param2 = 5`
-    Output.addParagraph().appendText("→ ").appendCode(
+    auto &P = Output.addParagraph().appendText("→ ").appendCode(
         llvm::to_string(*ReturnType));
-  }
 
+    if (!Documentation.Returns.empty())
+      P.appendText(": ").appendText(Documentation.Returns);
+  }
   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));
+
+    llvm::SmallVector<ParameterDocumentationOwned> ParamDocs =
+        Documentation.Parameters;
+
+    for (const auto &Param : *Parameters) {
+      auto &Paragraph = L.addItem().addParagraph();
+      Paragraph.appendCode(llvm::to_string(Param));
+
+      if (Param.Name.has_value()) {
+        auto ParamDoc = std::find_if(ParamDocs.begin(), ParamDocs.end(),
+                                     [Param](const auto &ParamDoc) {
+                                       return Param.Name == ParamDoc.Name;
+                                     });
+        if (ParamDoc != ParamDocs.end()) {
+          Paragraph.appendText(": ").appendText(ParamDoc->Description);
+          ParamDocs.erase(ParamDoc);
+        }
+      }
+    }
+
+    // We erased all parameters that matched, but some may still be left,
+    // usually typos. Let's also print them here.
+    for (const auto &ParamDoc : ParamDocs) {
+      L.addItem()
+          .addParagraph()
+          .appendCode(ParamDoc.Name)
+          .appendText(": ")
+          .appendText(ParamDoc.Description);
+    }
   }
 
   // Don't print Type after Parameters or ReturnType as this will just duplicate
@@ -1146,8 +1181,30 @@
     Output.addParagraph().appendText(OS.str());
   }
 
-  if (!Documentation.empty())
-    parseDocumentation(Documentation, Output);
+  if (!Documentation.Description.empty())
+    parseDocumentation(Documentation.Description, Output);
+
+  if (!Documentation.Warnings.empty()) {
+    Output.addRuler();
+    Output.addParagraph()
+        .appendText("Warning")
+        .appendText(Documentation.Warnings.size() > 1 ? "s" : "")
+        .appendText(": ");
+    markup::BulletList &L = Output.addBulletList();
+    for (const auto &Warning : Documentation.Warnings)
+      parseDocumentation(Warning, L.addItem());
+  }
+
+  if (!Documentation.Notes.empty()) {
+    Output.addRuler();
+    Output.addParagraph()
+        .appendText("Note")
+        .appendText(Documentation.Notes.size() > 1 ? "s" : "")
+        .appendText(": ");
+    markup::BulletList &L = Output.addBulletList();
+    for (const auto &Note : Documentation.Notes)
+      parseDocumentation(Note, L.addItem());
+  }
 
   if (!Definition.empty()) {
     Output.addRuler();
Index: clang-tools-extra/clangd/CodeCompletionStrings.h
===================================================================
--- clang-tools-extra/clangd/CodeCompletionStrings.h
+++ clang-tools-extra/clangd/CodeCompletionStrings.h
@@ -14,26 +14,28 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
 
+#include "clang/AST/CommentVisitor.h"
 #include "clang/Sema/CodeCompleteConsumer.h"
 
+#include "SymbolDocumentation.h"
+
 namespace clang {
 class ASTContext;
 
 namespace clangd {
 
-/// Gets a minimally formatted documentation comment of \p Result, with comment
-/// markers stripped. See clang::RawComment::getFormattedText() for the detailed
-/// explanation of how the comment text is transformed.
-/// Returns empty string when no comment is available.
+/// Gets the parsed doxygen documentation of \p Result.
+/// Returns an empty SymbolDocumentationOwned when no comment is available.
 /// If \p CommentsFromHeaders parameter is set, only comments from the main
 /// file will be returned. It is used to workaround crashes when parsing
 /// comments in the stale headers, coming from completion preamble.
-std::string getDocComment(const ASTContext &Ctx,
-                          const CodeCompletionResult &Result,
-                          bool CommentsFromHeaders);
+SymbolDocumentationOwned getDocumentation(const ASTContext &Ctx,
+                                          const CodeCompletionResult &Result,
+                                          bool CommentsFromHeaders);
 
-/// Similar to getDocComment, but returns the comment for a NamedDecl.
-std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &D);
+/// Similar to getDocumentation, but returns the comment for a NamedDecl.
+SymbolDocumentationOwned getDeclDocumentation(const ASTContext &Ctx,
+                                              const NamedDecl &D);
 
 /// Formats the signature for an item, as a display string and snippet.
 /// e.g. for const_reference std::vector<T>::at(size_type) const, this returns:
Index: clang-tools-extra/clangd/CodeCompletionStrings.cpp
===================================================================
--- clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -58,39 +58,42 @@
 
 } // namespace
 
-std::string getDocComment(const ASTContext &Ctx,
-                          const CodeCompletionResult &Result,
-                          bool CommentsFromHeaders) {
+SymbolDocumentationOwned getDocumentation(const ASTContext &Ctx,
+                                          const CodeCompletionResult &Result,
+                                          bool CommentsFromHeaders) {
+  // FIXME: CommentsFromHeaders seems to be unused? Is this a bug?
+
   // FIXME: clang's completion also returns documentation for RK_Pattern if they
   // contain a pattern for ObjC properties. Unfortunately, there is no API to
   // get this declaration, so we don't show documentation in that case.
   if (Result.Kind != CodeCompletionResult::RK_Declaration)
-    return "";
-  return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration())
-                                 : "";
+    return {};
+  return Result.getDeclaration()
+             ? getDeclDocumentation(Ctx, *Result.getDeclaration())
+             : SymbolDocumentationOwned{};
 }
 
-std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
+SymbolDocumentationOwned getDeclDocumentation(const ASTContext &Ctx,
+                                              const NamedDecl &Decl) {
   if (isa<NamespaceDecl>(Decl)) {
     // Namespaces often have too many redecls for any particular redecl comment
     // to be useful. Moreover, we often confuse file headers or generated
     // comments with namespace comments. Therefore we choose to just ignore
     // the comments for namespaces.
-    return "";
+    return {};
   }
   const RawComment *RC = getCompletionComment(Ctx, &Decl);
   if (!RC)
-    return "";
+    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());
-  if (!looksLikeDocComment(Doc))
-    return "";
-  // Clang requires source to be UTF-8, but doesn't enforce this in comments.
-  if (!llvm::json::isUTF8(Doc))
-    Doc = llvm::json::fixUTF8(Doc);
+
+  SymbolDocumentationOwned Doc = parseDoxygenComment(*RC, Ctx, &Decl);
+
+  if (!looksLikeDocComment(Doc.CommentText))
+    return {};
+
   return Doc;
 }
 
Index: clang-tools-extra/clangd/CodeComplete.cpp
===================================================================
--- clang-tools-extra/clangd/CodeComplete.cpp
+++ clang-tools-extra/clangd/CodeComplete.cpp
@@ -420,11 +420,11 @@
         }
       };
       if (C.IndexResult) {
-        SetDoc(C.IndexResult->Documentation);
+        SetDoc(C.IndexResult->Documentation.CommentText);
       } else if (C.SemaResult) {
-        const auto DocComment = getDocComment(*ASTCtx, *C.SemaResult,
-                                              /*CommentsFromHeaders=*/false);
-        SetDoc(formatDocumentation(*SemaCCS, DocComment));
+        const auto DocComment = getDocumentation(*ASTCtx, *C.SemaResult,
+                                                 /*CommentsFromHeaders=*/false);
+        SetDoc(formatDocumentation(*SemaCCS, DocComment.CommentText));
       }
     }
     if (Completion.Deprecated) {
@@ -977,8 +977,9 @@
       ScoredSignatures.push_back(processOverloadCandidate(
           Candidate, *CCS,
           Candidate.getFunction()
-              ? getDeclComment(S.getASTContext(), *Candidate.getFunction())
-              : ""));
+              ? getDeclDocumentation(S.getASTContext(),
+                                     *Candidate.getFunction())
+              : SymbolDocumentationOwned{}));
     }
 
     // Sema does not load the docs from the preamble, so we need to fetch extra
@@ -993,7 +994,7 @@
       }
       Index->lookup(IndexRequest, [&](const Symbol &S) {
         if (!S.Documentation.empty())
-          FetchedDocs[S.ID] = std::string(S.Documentation);
+          FetchedDocs[S.ID] = std::string(S.Documentation.CommentText);
       });
       vlog("SigHelp: requested docs for {0} symbols from the index, got {1} "
            "symbols with non-empty docs in the response",
@@ -1102,15 +1103,17 @@
 
   // FIXME(ioeric): consider moving CodeCompletionString logic here to
   // CompletionString.h.
-  ScoredSignature processOverloadCandidate(const OverloadCandidate &Candidate,
-                                           const CodeCompletionString &CCS,
-                                           llvm::StringRef DocComment) const {
+  ScoredSignature
+  processOverloadCandidate(const OverloadCandidate &Candidate,
+                           const CodeCompletionString &CCS,
+                           const SymbolDocumentationOwned &DocComment) const {
     SignatureInformation Signature;
     SignatureQualitySignals Signal;
     const char *ReturnType = nullptr;
 
     markup::Document OverloadComment;
-    parseDocumentation(formatDocumentation(CCS, DocComment), OverloadComment);
+    parseDocumentation(formatDocumentation(CCS, DocComment.CommentText),
+                       OverloadComment);
     Signature.documentation = renderDoc(OverloadComment, DocumentationFormat);
     Signal.Kind = Candidate.getKind();
 
Index: clang-tools-extra/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/CMakeLists.txt
+++ clang-tools-extra/clangd/CMakeLists.txt
@@ -97,6 +97,7 @@
   SemanticHighlighting.cpp
   SemanticSelection.cpp
   SourceCode.cpp
+  SymbolDocumentation.cpp
   QueryDriverDatabase.cpp
   TidyProvider.cpp
   TUScheduler.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D131853: [clangd] Add... Tom Praschan via Phabricator via cfe-commits

Reply via email to