[clang-tools-extra] [llvm] [clangd][WIP] Add doxygen parsing for Hover (PR #127451)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 17 12:05:07 PST 2025
https://github.com/tcottin updated https://github.com/llvm/llvm-project/pull/127451
>From 647304dc944911df72ab64dc07e26f78bb04a9d4 Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Mon, 17 Feb 2025 06:49:40 +0000
Subject: [PATCH 1/2] [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 at 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 at 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",
>From 0ed01383ae315981782bfafb4886cb5c96b49231 Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Mon, 17 Feb 2025 20:03:37 +0000
Subject: [PATCH 2/2] bump serialization version
---
.../clangd/index/Serialization.cpp | 2 +-
.../test/index-serialization/Inputs/sample.idx | Bin 470 -> 486 bytes
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/index/Serialization.cpp b/clang-tools-extra/clangd/index/Serialization.cpp
index d5a95e53505e7..b0323a57aeeed 100644
--- a/clang-tools-extra/clangd/index/Serialization.cpp
+++ b/clang-tools-extra/clangd/index/Serialization.cpp
@@ -508,7 +508,7 @@ readCompileCommand(Reader CmdReader, llvm::ArrayRef<llvm::StringRef> Strings) {
// The current versioning scheme is simple - non-current versions are rejected.
// If you make a breaking change, bump this version number to invalidate stored
// data. Later we may want to support some backward compatibility.
-constexpr static uint32_t Version = 20;
+constexpr static uint32_t Version = 21;
llvm::Expected<IndexFileIn> readRIFF(llvm::StringRef Data,
SymbolOrigin Origin) {
diff --git a/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx b/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx
index 6368e7145b1e4d628708f40d684bc8db1ef7f94d..51f06cd725d8ec0610dbdb3196dad04f73dad84b 100644
GIT binary patch
delta 236
zcmcb{{EWFi$kWa39wP&TbBbq0ZfZ#)3j+g#C=eHy6lFF8=}$mhF=uP<LEb|KJPgKO
z;vRp`Pi7A~ZqV7-And-4yY=P5BsMwqx$lY}Hc0JTBGRX1y?d=~`_kQBk;c9jH&@SX
zd0w#2hm&F6qH^gOoHOKg`KC&zwx7A7f6?`ZQtmphAKI*Q&Mf at q6m4>;E9U--P%E9<
zHF*g)*e=gG{^R>2DZ3=U2sVb|%G{)BK!;RTEB^dBCsbd6iIs_gotvE-h=2r81_&me
cR-gDrkqyjdo6O6oI$4KNk{KvJ*@LkU0IuLtcK`qY
delta 231
zcmaFHe2uw2$kWa393umRbBbq0ZfZ#)3j+g#2oM*S6lHb;X%3)V#hkspj(p7qJS?9@
z>=^bvzu~MpFHvawlA<>nW*y(YvVP^a?b$Nxu-}eEes#mP-LH;rHS<gqdp~Qt3p3xh
zb={vt<XSbE4y>A*5Ey=PLT3B5udXL~XME?nCamJ-Q`xufl-Z?+vh`}slPyI~mh`yo
zRhZ6S71$VgtIST~+A))**ZxIB9~Ng(iNC>6T$!6x2Xs_rwc^j8b3*k6m{^$@*xA_G
dxWUB4lj;+{NlzAMRGsX^D9j8LpB%y12LRGKRtf+B
More information about the cfe-commits
mailing list