https://github.com/tcottin created https://github.com/llvm/llvm-project/pull/127451
With this PR I try to revive clangd/clangd#529. I applied [this patch](https://aur.archlinux.org/cgit/aur.git/commit/hover-doxygen-trunk.patch?h=clangd-opt&id=b00d4e961c78e2126b7226e924595239e9ce3cae) and rebased it to main. Note: The original author of the patch is @tom-anders and there is this [abandoned phabricator review](https://reviews.llvm.org/D129972). In addition to applying the patch, I fixed the parsing of block commands with arguments to solve one of the open points of the issue. I also changed the checks for specific commands from name to using the `comments::CommandInfo` to check whether a command is a brief or return command. This allows to handle all brief (e.g. `\brief`, `\short`) and return (e.g. `\return`, `\returns`) the same without the need to check for all the command names individually. There was a merge conflict with #67802 which I solved without any failing tests but I am not sure yet if this is really correct. According to @aaronliu0130 we also need to consider #78491 for this change which I did not do yet. >From 647304dc944911df72ab64dc07e26f78bb04a9d4 Mon Sep 17 00:00:00 2001 From: Tim Cottin <timcot...@gmx.de> Date: Mon, 17 Feb 2025 06:49:40 +0000 Subject: [PATCH] [clangd][WIP] Add doxygen parsing for Hover --- clang-tools-extra/clangd/CMakeLists.txt | 1 + clang-tools-extra/clangd/CodeComplete.cpp | 27 +- .../clangd/CodeCompletionStrings.cpp | 35 +-- .../clangd/CodeCompletionStrings.h | 19 +- clang-tools-extra/clangd/Hover.cpp | 82 ++++++- clang-tools-extra/clangd/Hover.h | 3 +- .../clangd/SymbolDocumentation.cpp | 231 ++++++++++++++++++ .../clangd/SymbolDocumentation.h | 101 ++++++++ clang-tools-extra/clangd/index/Merge.cpp | 2 +- .../clangd/index/Serialization.cpp | 55 ++++- clang-tools-extra/clangd/index/Symbol.h | 18 +- .../clangd/index/SymbolCollector.cpp | 28 +-- .../clangd/index/YAMLSerialization.cpp | 25 ++ .../index-serialization/Inputs/sample.cpp | 7 +- .../clangd/unittests/CodeCompleteTests.cpp | 4 +- .../unittests/CodeCompletionStringsTests.cpp | 118 ++++++++- .../clangd/unittests/HoverTests.cpp | 195 +++++++++++---- .../clangd/unittests/IndexTests.cpp | 16 +- .../clangd/unittests/SerializationTests.cpp | 33 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 2 +- .../unittests/SymbolDocumentationMatchers.h | 51 ++++ .../clang-tools-extra/clangd/BUILD.gn | 1 + 22 files changed, 912 insertions(+), 142 deletions(-) create mode 100644 clang-tools-extra/clangd/SymbolDocumentation.cpp create mode 100644 clang-tools-extra/clangd/SymbolDocumentation.h create mode 100644 clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 6f10afe4a5625..2fda21510f046 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC SemanticHighlighting.cpp SemanticSelection.cpp SourceCode.cpp + SymbolDocumentation.cpp SystemIncludeExtractor.cpp TidyProvider.cpp TUScheduler.cpp diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index a8182ce98ebe0..6c237dcf41e18 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -504,11 +504,11 @@ struct CodeCompletionBuilder { } }; 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) { @@ -1106,8 +1106,9 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { 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 @@ -1122,7 +1123,7 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { } 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", @@ -1231,15 +1232,17 @@ class SignatureHelpCollector final : public CodeCompleteConsumer { // 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(); @@ -1898,7 +1901,7 @@ class CodeCompleteFlow { return; auto &C = Output.Completions[SymbolToCompletion.at(S.ID)]; C.Documentation.emplace(); - parseDocumentation(S.Documentation, *C.Documentation); + parseDocumentation(S.Documentation.CommentText, *C.Documentation); }); } diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index 9b4442b0bb76f..d150e1262fe00 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -80,39 +80,42 @@ bool shouldPatchPlaceholder0(CodeCompletionResult::ResultKind ResultKind, } // 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; } diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.h b/clang-tools-extra/clangd/CodeCompletionStrings.h index fa81ad64d406c..8a454a7d33770 100644 --- 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<T>::at(size_type) const, this returns: diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 3ab3d89030520..ce7c46be5c8f4 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -347,7 +347,7 @@ void enhanceFromIndex(HoverInfo &Hover, const NamedDecl &ND, LookupRequest Req; Req.IDs.insert(ID); Index->lookup(Req, [&](const Symbol &S) { - Hover.Documentation = std::string(S.Documentation); + Hover.Documentation = S.Documentation.toOwned(); }); } @@ -625,10 +625,11 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP, 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; @@ -682,7 +683,8 @@ getPredefinedExprHoverContents(const PredefinedExpr &PE, ASTContext &Ctx, 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); @@ -856,7 +858,7 @@ HoverInfo getDeducedTypeHoverContents(QualType QT, const syntax::Token &Tok, 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); } } @@ -956,7 +958,8 @@ std::optional<HoverInfo> getHoverContents(const Attr *A, ParsedAST &AST) { 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; } @@ -1455,6 +1458,10 @@ markup::Document HoverInfo::present() const { // 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) { @@ -1463,15 +1470,44 @@ markup::Document HoverInfo::present() const { // 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 @@ -1518,8 +1554,30 @@ markup::Document HoverInfo::present() const { 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/Hover.h b/clang-tools-extra/clangd/Hover.h index fe689de44732e..765df1ebb9cce 100644 --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -11,6 +11,7 @@ #include "ParsedAST.h" #include "Protocol.h" +#include "SymbolDocumentation.h" #include "support/Markup.h" #include "clang/Index/IndexSymbol.h" #include <optional> @@ -73,7 +74,7 @@ struct HoverInfo { std::string Provider; std::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"; diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp new file mode 100644 index 0000000000000..bfe3c9bd6a116 --- /dev/null +++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp @@ -0,0 +1,231 @@ +//===--- 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/CommentCommandTraits.h" +#include "clang/AST/CommentVisitor.h" +#include "llvm/ADT/StringRef.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); +} + +void ensureUTF8(llvm::MutableArrayRef<std::string> Strings) { + for (auto &Str : Strings) { + ensureUTF8(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()); + + // Some commands have arguments, like \throws. + // The arguments are not part of the paragraph. + // We need reconstruct them here. + if (B->getNumArgs() > 0) { + for (unsigned I = 0; I < B->getNumArgs(); ++I) { + Out << " "; + Out << B->getArgText(I); + } + if (B->hasNonWhitespaceParagraph()) + Out << " "; + } + + 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::InlineCommandRenderKind::Monospaced: + return "`"; + case comments::InlineCommandRenderKind::Bold: + return "**"; + case comments::InlineCommandRenderKind::Emphasized: + 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 comments::CommandTraits &Traits = Ctx.getCommentCommandTraits(); + const comments::CommandInfo *Info = Traits.getCommandInfo(B->getCommandID()); + + // Visit B->getParagraph() for commands that we have special fields for, + // so that the command name won't be included in the string. + // Otherwise, we want to keep the command name, so visit B itself. + if (Info->IsBriefCommand) { + BlockCommentToString(Output.Brief, Ctx).visit(B->getParagraph()); + } else if (Info->IsReturnsCommand) { + BlockCommentToString(Output.Returns, Ctx).visit(B->getParagraph()); + } else { + const llvm::StringRef CommandName = B->getCommandName(Traits); + 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) { + if (!Output.Description.empty()) + Output.Description += "\n\n"; + 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); + + ensureUTF8(Doc.Notes); + ensureUTF8(Doc.Warnings); + + 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 StrOut, class StrIn> +SymbolDocumentation<StrOut> convert(const SymbolDocumentation<StrIn> &In) { + SymbolDocumentation<StrOut> 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<StrOut>{ + StrOut(ParamDoc.Name), StrOut(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 diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h new file mode 100644 index 0000000000000..77bd909278024 --- /dev/null +++ b/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 diff --git a/clang-tools-extra/clangd/index/Merge.cpp b/clang-tools-extra/clangd/index/Merge.cpp index aecca38a885b6..45ca6cf7f2b63 100644 --- a/clang-tools-extra/clangd/index/Merge.cpp +++ b/clang-tools-extra/clangd/index/Merge.cpp @@ -261,7 +261,7 @@ Symbol mergeSymbol(const Symbol &L, const Symbol &R) { 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 diff --git a/clang-tools-extra/clangd/index/Serialization.cpp b/clang-tools-extra/clangd/index/Serialization.cpp index f03839599612c..d5a95e53505e7 100644 --- a/clang-tools-extra/clangd/index/Serialization.cpp +++ b/clang-tools-extra/clangd/index/Serialization.cpp @@ -283,6 +283,57 @@ SymbolLocation readLocation(Reader &Data, 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 @@ void writeSymbol(const Symbol &Sym, const StringTableOut &Strings, 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 @@ Symbol readSymbol(Reader &Data, llvm::ArrayRef<llvm::StringRef> Strings, 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)) diff --git a/clang-tools-extra/clangd/index/Symbol.h b/clang-tools-extra/clangd/index/Symbol.h index 62c47ddfc5758..9c6c94f4b6857 100644 --- a/clang-tools-extra/clangd/index/Symbol.h +++ b/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" @@ -76,7 +77,7 @@ struct Symbol { /// 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. @@ -174,7 +175,20 @@ template <typename Callback> void visitStrings(Symbol &S, const Callback &CB) { 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) { diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 1de7faf81746e..9869af456a2bf 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -1080,19 +1080,17 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, *ASTCtx, *PP, CodeCompletionContext::CCC_Symbol, *CompletionAllocator, *CompletionTUInfo, /*IncludeBriefComments*/ false); - std::string DocComment; - std::string Documentation; + SymbolDocumentationOwned Documentation; bool AlreadyHasDoc = S.Flags & Symbol::HasDocComment; if (!AlreadyHasDoc) { - DocComment = getDocComment(Ctx, SymbolCompletion, - /*CommentsFromHeaders=*/true); - Documentation = formatDocumentation(*CCS, DocComment); + Documentation = + getDocumentation(Ctx, SymbolCompletion, /*CommentsFromHeaders=*/true); } const auto UpdateDoc = [&] { if (!AlreadyHasDoc) { - if (!DocComment.empty()) + if (!Documentation.empty()) S.Flags |= Symbol::HasDocComment; - S.Documentation = Documentation; + S.Documentation = Documentation.toRef(); } }; if (!(S.Flags & Symbol::IndexedForCodeCompletion)) { @@ -1142,24 +1140,14 @@ void SymbolCollector::addDefinition(const NamedDecl &ND, const Symbol &DeclSym, // FIXME: use the result to filter out symbols. S.Definition = *DefLoc; - std::string DocComment; - std::string Documentation; if (!SkipDocCheck && !(S.Flags & Symbol::HasDocComment) && (llvm::isa<FunctionDecl>(ND) || llvm::isa<CXXMethodDecl>(ND))) { CodeCompletionResult SymbolCompletion(&getTemplateOrThis(ND), 0); - const auto *CCS = SymbolCompletion.CreateCodeCompletionString( - *ASTCtx, *PP, CodeCompletionContext::CCC_Symbol, *CompletionAllocator, - *CompletionTUInfo, - /*IncludeBriefComments*/ false); - DocComment = getDocComment(ND.getASTContext(), SymbolCompletion, + SymbolDocumentationOwned Documentation = getDocumentation(ND.getASTContext(), SymbolCompletion, /*CommentsFromHeaders=*/true); - if (!S.Documentation.empty()) - Documentation = S.Documentation.str() + '\n' + DocComment; - else - Documentation = formatDocumentation(*CCS, DocComment); - if (!DocComment.empty()) + if (!Documentation.empty()) S.Flags |= Symbol::HasDocComment; - S.Documentation = Documentation; + S.Documentation = Documentation.toRef(); } Symbols.insert(S); diff --git a/clang-tools-extra/clangd/index/YAMLSerialization.cpp b/clang-tools-extra/clangd/index/YAMLSerialization.cpp index 214a847b5eddb..e87c777d8966b 100644 --- a/clang-tools-extra/clangd/index/YAMLSerialization.cpp +++ b/clang-tools-extra/clangd/index/YAMLSerialization.cpp @@ -34,6 +34,7 @@ struct YIncludeHeaderWithReferences; 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) LLVM_YAML_IS_SEQUENCE_VECTOR(YIncludeHeaderWithReferences) namespace { @@ -79,11 +80,13 @@ namespace yaml { 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; @@ -221,6 +224,28 @@ struct NormalizedIncludeHeaders { llvm::SmallVector<YIncludeHeaderWithReferences, 1> Headers; }; +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); diff --git a/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp b/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp index f9f13b930d62e..4efbf004cfcc9 100644 --- a/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp +++ b/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 {} }; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index b12f8275b8a26..6ce566b60d2d2 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2649,9 +2649,9 @@ TEST(SignatureHelpTest, InstantiatedSignatures) { 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( diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp index de5f533d31645..901c59f301a59 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp +++ b/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" @@ -61,12 +62,121 @@ TEST_F(CompletionStringTest, DocumentationWithAnnotation) { "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) { diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 69f6df46c87ce..e2a1dd5688e44 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -10,6 +10,7 @@ #include "Annotations.h" #include "Config.h" #include "Hover.h" +#include "SymbolDocumentationMatchers.h" #include "TestFS.h" #include "TestIndex.h" #include "TestTU.h" @@ -50,7 +51,8 @@ TEST(Hover, Structured) { 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 ()"; @@ -67,7 +69,8 @@ TEST(Hover, Structured) { 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 ()"; @@ -160,8 +163,8 @@ TEST(Hover, Structured) { [](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]"; }}, @@ -174,8 +177,8 @@ TEST(Hover, Structured) { [](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. @@ -826,7 +829,8 @@ class Foo final {})cpp"; 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( @@ -878,7 +882,8 @@ class Foo final {})cpp"; 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(); @@ -894,7 +899,8 @@ class Foo final {})cpp"; 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(); @@ -913,7 +919,8 @@ class Foo final {})cpp"; 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(); @@ -933,7 +940,8 @@ class Foo final {})cpp"; 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(); @@ -1420,7 +1428,7 @@ class Foo final {})cpp"; 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); @@ -1713,7 +1721,8 @@ TEST(Hover, All) { 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"}, std::nullopt, std::nullopt}, @@ -1732,7 +1741,8 @@ TEST(Hover, All) { 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"}, std::nullopt, std::nullopt}, @@ -1880,7 +1890,8 @@ TEST(Hover, All) { HI.NamespaceScope = ""; HI.Definition = "typedef int Foo"; HI.Type = "int"; - HI.Documentation = "Typedef"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Typedef"); }}, { R"cpp(// Typedef with embedded definition @@ -1895,7 +1906,8 @@ TEST(Hover, All) { 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 @@ -1942,7 +1954,7 @@ TEST(Hover, All) { HI.NamespaceScope = "ns::"; HI.Type = "void ()"; HI.Definition = "void foo()"; - HI.Documentation = ""; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly(""); HI.ReturnType = "void"; HI.Parameters = std::vector<HoverInfo::Param>{}; }}, @@ -2016,10 +2028,18 @@ TEST(Hover, All) { 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() {} @@ -2030,7 +2050,22 @@ TEST(Hover, All) { 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>{}; }}, @@ -2048,7 +2083,8 @@ TEST(Hover, All) { HI.Kind = index::SymbolKind::Enum; HI.NamespaceScope = ""; HI.Definition = "enum Hello {}"; - HI.Documentation = "Enum declaration"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Enum declaration"); }}, { R"cpp(// Enumerator @@ -2119,7 +2155,8 @@ TEST(Hover, All) { 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)"; }}, @@ -2171,7 +2208,8 @@ TEST(Hover, All) { 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 @@ -2208,7 +2246,8 @@ TEST(Hover, All) { 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 @@ -2377,7 +2416,8 @@ TEST(Hover, All) { 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 @@ -2390,7 +2430,8 @@ TEST(Hover, All) { 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 @@ -2403,7 +2444,8 @@ TEST(Hover, All) { 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 @@ -2417,7 +2459,8 @@ TEST(Hover, All) { 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 @@ -2431,7 +2474,8 @@ TEST(Hover, All) { 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 @@ -2445,7 +2489,8 @@ TEST(Hover, All) { 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 @@ -2458,7 +2503,8 @@ TEST(Hover, All) { 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 @@ -2548,8 +2594,8 @@ TEST(Hover, All) { 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. @@ -2632,7 +2678,8 @@ TEST(Hover, All) { 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 @@ -2644,7 +2691,8 @@ TEST(Hover, All) { 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 @@ -2735,7 +2783,8 @@ TEST(Hover, All) { 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 @@ -3056,7 +3105,8 @@ TEST(Hover, All) { 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()); }}, { R"cpp( @@ -3091,13 +3141,13 @@ TEST(Hover, All) { HI.NamespaceScope = ""; HI.Definition = "bool operator==(const Foo &) const noexcept = default"; - HI.Documentation = ""; }}, }; // 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 = @@ -3130,7 +3180,7 @@ TEST(Hover, All) { 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); @@ -3305,7 +3355,8 @@ TEST(Hover, DocsFromIndex) { 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 = @@ -3314,7 +3365,7 @@ TEST(Hover, DocsFromIndex) { 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)); } } @@ -3339,7 +3390,8 @@ TEST(Hover, DocsFromAST) { 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"))); } } @@ -3400,7 +3452,9 @@ TEST(Hover, DocsFromMostSpecial) { 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))); } } } @@ -3432,7 +3486,14 @@ TEST(Hover, Present) { {{"typename"}, std::string("T"), std::nullopt}, {{"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"; @@ -3440,8 +3501,17 @@ TEST(Hover, Present) { }, R"(class foo +brief Size: 10 bytes -documentation +details + +Warnings: +- warning1 +- warning2 + +Notes: +- note1 +- note2 template <typename T, typename C = bool> class Foo {})", }, @@ -3460,17 +3530,37 @@ template <typename T, typename C = bool> class Foo {})", 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) {}", @@ -3884,17 +3974,20 @@ TEST(Hover, SpaceshipTemplateNoCrash) { template <typename T> struct S { - // Foo bar baz + /// Foo bar baz friend auto operator<=>(S, S) = default; }; - static_assert(S<void>() =^= S<void>()); + static_assert((S<void>() <^=> S<void>()) == std::strong_ordering::equal); )cpp"); TestTU TU = TestTU::withCode(T.code()); TU.ExtraArgs.push_back("-std=c++20"); auto AST = TU.build(); auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr); - EXPECT_EQ(HI->Documentation, ""); + + ASSERT_THAT( + HI->Documentation, + matchesDoc(SymbolDocumentationOwned::descriptionOnly("Foo bar baz"))); } TEST(Hover, ForwardStructNoCrash) { diff --git a/clang-tools-extra/clangd/unittests/IndexTests.cpp b/clang-tools-extra/clangd/unittests/IndexTests.cpp index a66680d39c87d..742910e602a2e 100644 --- a/clang-tools-extra/clangd/unittests/IndexTests.cpp +++ b/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 @@ TEST(MergeTest, Merge) { 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 @@ TEST(MergeTest, Merge) { 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 @@ TEST(MergeIndexTest, NonDocumentation) { 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, "") { diff --git a/clang-tools-extra/clangd/unittests/SerializationTests.cpp b/clang-tools-extra/clangd/unittests/SerializationTests.cpp index 2a7a6c36d3d17..3de53cf923857 100644 --- a/clang-tools-extra/clangd/unittests/SerializationTests.cpp +++ b/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" @@ -49,7 +50,22 @@ Scope: 'clang::' 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' @@ -153,7 +169,20 @@ TEST(SerializationTest, YAMLConversions) { 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); diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 7a9703c744e93..547e9b1db6632 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -53,7 +53,7 @@ MATCHER_P(labeled, Label, "") { 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; } diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h b/clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h new file mode 100644 index 0000000000000..12c955c458cc7 --- /dev/null +++ b/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 diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn index b609d4a7462fb..f8c4838ab7ee3 100644 --- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn +++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn @@ -122,6 +122,7 @@ static_library("clangd") { "SemanticHighlighting.cpp", "SemanticSelection.cpp", "SourceCode.cpp", + "SymbolDocumentation.cpp", "SystemIncludeExtractor.cpp", "TUScheduler.cpp", "TidyProvider.cpp", _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits