diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -427,8 +427,9 @@ if (C.IndexResult) { SetDoc(C.IndexResult->Documentation); } else if (C.SemaResult) { - const auto DocComment = getDocComment(*ASTCtx, *C.SemaResult, - /*CommentsFromHeaders=*/false); + const auto DocComment = getDocumentation(*ASTCtx, *C.SemaResult, + /*CommentsFromHeaders=*/false) + .CommentText; SetDoc(formatDocumentation(*SemaCCS, DocComment)); } } @@ -982,7 +983,9 @@ ScoredSignatures.push_back(processOverloadCandidate( Candidate, *CCS, Candidate.getFunction() - ? getDeclComment(S.getASTContext(), *Candidate.getFunction()) + ? getDeclDocumentation(S.getASTContext(), + *Candidate.getFunction()) + .CommentText : "")); } diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.h b/clang-tools-extra/clangd/CodeCompletionStrings.h --- a/clang-tools-extra/clangd/CodeCompletionStrings.h +++ b/clang-tools-extra/clangd/CodeCompletionStrings.h @@ -16,24 +16,25 @@ #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::at(size_type) const, this returns: diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/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(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; } diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h --- 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" @@ -68,7 +69,7 @@ std::string Name; llvm::Optional 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"; diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -333,7 +333,8 @@ LookupRequest Req; Req.IDs.insert(ID); Index->lookup(Req, [&](const Symbol &S) { - Hover.Documentation = std::string(S.Documentation); + Hover.Documentation = + SymbolDocumentationOwned::descriptionOnly(std::string(S.Documentation)); }); } @@ -592,10 +593,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; @@ -649,7 +651,8 @@ HoverInfo HI; HI.Name = PE.getIdentKindName(); HI.Kind = index::SymbolKind::Variable; - HI.Documentation = "Name of the current function (predefined variable)"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Name of the current function (predefined variable)"); if (const StringLiteral *Name = PE.getFunctionName()) { HI.Value.emplace(); llvm::raw_string_ostream OS(*HI.Value); @@ -769,7 +772,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); } } @@ -836,7 +839,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; } @@ -1172,6 +1176,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) { @@ -1180,15 +1188,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 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 @@ -1232,8 +1269,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(); diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -913,8 +913,9 @@ *CompletionTUInfo, /*IncludeBriefComments*/ false); std::string Documentation = - formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion, - /*CommentsFromHeaders=*/true)); + formatDocumentation(*CCS, getDocumentation(Ctx, SymbolCompletion, + /*CommentsFromHeaders=*/true) + .CommentText); if (!(S.Flags & Symbol::IndexedForCodeCompletion)) { if (Opts.StoreAllDocumentation) S.Documentation = Documentation; diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp @@ -58,12 +58,39 @@ "Annotation: Ano\n\nIs this brief?"); } -TEST_F(CompletionStringTest, GetDeclCommentBadUTF8) { +TEST_F(CompletionStringTest, GetDeclDocumentationBadUTF8) { // is not a valid byte here, should be replaced by encoded . - 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) { @@ -144,9 +171,7 @@ ->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); Case.ExpectedBuilder(ExpectedDoc); - const RawComment *RC = getCompletionComment(Ctx, &Decl); - EXPECT_THAT(RC, testing::NotNull()); - EXPECT_THAT(parseDoxygenComment(*RC, Ctx, &Decl), matchesDoc(ExpectedDoc)); + EXPECT_THAT(getDeclDocumentation(Ctx, Decl), matchesDoc(ExpectedDoc)); } } diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp --- 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 "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 ()"; @@ -148,8 +151,8 @@ [](HoverInfo &HI) { HI.Name = "__func__"; HI.Kind = index::SymbolKind::Variable; - HI.Documentation = - "Name of the current function (predefined variable)"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Name of the current function (predefined variable)"); HI.Value = "\"foo\""; HI.Type = "const char[4]"; }}, @@ -162,8 +165,8 @@ [](HoverInfo &HI) { HI.Name = "__func__"; HI.Kind = index::SymbolKind::Variable; - HI.Documentation = - "Name of the current function (predefined variable)"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Name of the current function (predefined variable)"); HI.Type = "const char[]"; }}, // Anon namespace and local scope. @@ -741,7 +744,8 @@ HI.Definition = "template <> class Foo"; // 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( @@ -793,7 +797,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(); @@ -809,7 +814,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(); @@ -828,7 +834,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(); @@ -848,7 +855,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(); @@ -1140,7 +1148,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); @@ -1417,7 +1425,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}, @@ -1436,7 +1445,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}, @@ -1584,7 +1594,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 @@ -1599,7 +1610,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 @@ -1646,7 +1658,7 @@ HI.NamespaceScope = "ns::"; HI.Type = "void ()"; HI.Definition = "void foo()"; - HI.Documentation = ""; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly(""); HI.ReturnType = "void"; HI.Parameters = std::vector{}; }}, @@ -1714,10 +1726,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() {} @@ -1728,7 +1748,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{}; }}, @@ -1746,7 +1781,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 @@ -1817,7 +1853,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)"; }}, @@ -1869,7 +1906,8 @@ HI.NamespaceScope = ""; HI.Type = "int ()"; HI.Definition = "template <> int foo()"; - HI.Documentation = "Templated function"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Templated function"); HI.ReturnType = "int"; HI.Parameters = std::vector{}; // FIXME: We should populate template parameters with arguments in @@ -1906,7 +1944,8 @@ HI.Definition = "void indexSymbol()"; HI.ReturnType = "void"; HI.Parameters = std::vector{}; - HI.Documentation = "comment from index"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("comment from index"); }}, { R"cpp(// Simple initialization with auto @@ -2075,7 +2114,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 @@ -2088,7 +2128,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 @@ -2101,7 +2142,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 @@ -2115,7 +2157,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 @@ -2129,7 +2172,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 @@ -2143,7 +2187,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 @@ -2156,7 +2201,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 @@ -2246,8 +2292,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. @@ -2317,7 +2363,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 @@ -2329,7 +2376,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "templ"; - HI.Documentation = "auto on alias"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("auto on alias"); }}, { R"cpp(// Undeduced auto declaration @@ -2420,7 +2468,8 @@ HI.Kind = index::SymbolKind::Struct; HI.NamespaceScope = ""; HI.Name = "cls>>"; - HI.Documentation = "type of nested templates."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "type of nested templates."); }}, { R"cpp(// type with decltype @@ -2714,7 +2763,8 @@ 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()); }}, }; @@ -2753,7 +2803,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); @@ -2786,7 +2836,9 @@ 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, + matchesDoc(SymbolDocumentationOwned::descriptionOnly( + std::string(IndexSym.Documentation)))); } } @@ -2811,7 +2863,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"))); } } @@ -2849,7 +2902,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))); } } } @@ -2881,7 +2936,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 class Foo {}"; HI.Name = "foo"; @@ -2889,8 +2951,17 @@ }, R"(class foo +brief Size: 10 bytes -documentation +details + +Warnings: +- warning1 +- warning2 + +Notes: +- note1 +- note2 template class Foo {})", }, @@ -2909,17 +2980,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) {}", @@ -3291,7 +3382,7 @@ template struct S { - // Foo bar baz + /// Foo bar baz friend auto operator<=>(S, S) = default; }; static_assert(S() =^= S()); @@ -3301,7 +3392,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) {