[clang] [clang-tools-extra] [llvm] [clangd][WIP] Add doxygen parsing using standalone doxygen parser (PR #128591)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 11 13:08:54 PDT 2025
https://github.com/tcottin updated https://github.com/llvm/llvm-project/pull/128591
>From 1af93ca8fe54daf111b00ff62e9ef70cf2b97b53 Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Tue, 11 Mar 2025 20:08:32 +0000
Subject: [PATCH] [clangd] Add doxygen parsing for hover information
---
clang-tools-extra/clangd/CMakeLists.txt | 1 +
.../clangd/CodeCompletionStrings.cpp | 74 ++-
.../clangd/CodeCompletionStrings.h | 23 +
clang-tools-extra/clangd/Hover.cpp | 141 ++---
clang-tools-extra/clangd/Hover.h | 54 +-
.../clangd/SymbolDocumentation.cpp | 503 ++++++++++++++++++
.../clangd/SymbolDocumentation.h | 92 ++++
clang-tools-extra/clangd/support/Markup.cpp | 36 ++
clang-tools-extra/clangd/support/Markup.h | 11 +-
.../unittests/CodeCompletionStringsTests.cpp | 219 ++++++++
.../clangd/unittests/HoverTests.cpp | 127 ++++-
.../clangd/unittests/support/MarkupTests.cpp | 30 ++
clang/include/clang/AST/Comment.h | 21 +
clang/include/clang/AST/CommentSema.h | 1 +
clang/lib/AST/CommentParser.cpp | 5 +-
clang/lib/AST/CommentSema.cpp | 7 +-
.../clang-tools-extra/clangd/BUILD.gn | 1 +
17 files changed, 1180 insertions(+), 166 deletions(-)
create mode 100644 clang-tools-extra/clangd/SymbolDocumentation.cpp
create mode 100644 clang-tools-extra/clangd/SymbolDocumentation.h
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 6f10afe4a5625..2fda21510f046 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC
SemanticHighlighting.cpp
SemanticSelection.cpp
SourceCode.cpp
+ SymbolDocumentation.cpp
SystemIncludeExtractor.cpp
TidyProvider.cpp
TUScheduler.cpp
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index 9b4442b0bb76f..f307866176ca5 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -9,6 +9,12 @@
#include "CodeCompletionStrings.h"
#include "clang-c/Index.h"
#include "clang/AST/ASTContext.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentLexer.h"
+#include "clang/AST/CommentParser.h"
+#include "clang/AST/CommentSema.h"
+#include "clang/AST/Decl.h"
#include "clang/AST/RawCommentList.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Sema/CodeCompleteConsumer.h"
@@ -100,14 +106,25 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
// the comments for namespaces.
return "";
}
- const RawComment *RC = getCompletionComment(Ctx, &Decl);
- if (!RC)
- return "";
- // Sanity check that the comment does not come from the PCH. We choose to not
- // write them into PCH, because they are racy and slow to load.
- assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
- std::string Doc =
- RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+
+ std::string Doc;
+
+ if (isa<ParmVarDecl>(Decl)) {
+ // Parameters are documented in the function comment.
+ if (const auto *FD = dyn_cast<FunctionDecl>(Decl.getDeclContext()))
+ Doc = getParamDocString(Ctx.getCommentForDecl(FD, nullptr),
+ Decl.getName(), Ctx.getCommentCommandTraits());
+ } else {
+
+ const RawComment *RC = getCompletionComment(Ctx, &Decl);
+ if (!RC)
+ return "";
+ // Sanity check that the comment does not come from the PCH. We choose to
+ // not write them into PCH, because they are racy and slow to load.
+ assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
+ Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+ }
+
if (!looksLikeDocComment(Doc))
return "";
// Clang requires source to be UTF-8, but doesn't enforce this in comments.
@@ -316,5 +333,46 @@ std::string getReturnType(const CodeCompletionString &CCS) {
return "";
}
+void docCommentToMarkup(
+ markup::Document &Doc, llvm::StringRef Comment,
+ llvm::BumpPtrAllocator &Allocator, comments::CommandTraits &Traits,
+ std::optional<SymbolPrintedType> SymbolType,
+ std::optional<SymbolPrintedType> SymbolReturnType,
+ const std::optional<std::vector<SymbolParam>> &SymbolParameters) {
+
+ // The comment lexer expects doxygen markers, so add them back.
+ // We need to use the /// style doxygen markers because the comment could
+ // contain the closing the closing tag "*/" of a C Style "/** */" comment
+ // which would break the parsing if we would just enclose the comment text
+ // with "/** */".
+ std::string CommentWithMarkers = "///";
+ for (char C : Comment) {
+ if (C == '\n') {
+ CommentWithMarkers += "\n///";
+ } else {
+ CommentWithMarkers += C;
+ }
+ }
+ SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers);
+
+ SourceManager &SourceMgr = SourceMgrForFile.get();
+ // The doxygen Sema requires a Diagostics consumer, since it reports warnings
+ // e.g. when parameters are not documented correctly.
+ // These warnings are not relevant for us, so we can ignore them.
+ SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer);
+
+ comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits,
+ /*PP=*/nullptr);
+ comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits,
+ SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()),
+ CommentWithMarkers.data(),
+ CommentWithMarkers.data() + CommentWithMarkers.size());
+ comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(),
+ Traits);
+
+ fullCommentToMarkupDocument(Doc, P.parseFullComment(), Traits, SymbolType,
+ SymbolReturnType, SymbolParameters);
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.h b/clang-tools-extra/clangd/CodeCompletionStrings.h
index fa81ad64d406c..b440849f0eb49 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.h
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.h
@@ -14,11 +14,17 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
+#include "SymbolDocumentation.h"
#include "clang/Sema/CodeCompleteConsumer.h"
namespace clang {
class ASTContext;
+namespace comments {
+class CommandTraits;
+class FullComment;
+} // namespace comments
+
namespace clangd {
/// Gets a minimally formatted documentation comment of \p Result, with comment
@@ -67,6 +73,23 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
/// is usually the return type of a function.
std::string getReturnType(const CodeCompletionString &CCS);
+/// \brief Parse the \p Comment as doxygen comment and save the result in the
+/// given markup Document \p Doc.
+///
+/// It is assumed that comment markers have already been stripped (e.g. via
+/// getDocComment()).
+///
+/// This uses the Clang doxygen comment parser to parse the comment, and then
+/// converts the parsed comment to a markup::Document. The resulting document is
+/// a combination of the symbol information \p SymbolType, \p SymbolReturnType,
+/// and \p SymbolParameters and the parsed doxygen comment.
+void docCommentToMarkup(
+ markup::Document &Doc, llvm::StringRef Comment,
+ llvm::BumpPtrAllocator &Allocator, comments::CommandTraits &Traits,
+ std::optional<SymbolPrintedType> SymbolType,
+ std::optional<SymbolPrintedType> SymbolReturnType,
+ const std::optional<std::vector<SymbolParam>> &SymbolParameters);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 3ab3d89030520..9b98ac6671374 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -17,6 +17,7 @@
#include "ParsedAST.h"
#include "Selection.h"
#include "SourceCode.h"
+#include "SymbolDocumentation.h"
#include "clang-include-cleaner/Analysis.h"
#include "clang-include-cleaner/IncludeSpeller.h"
#include "clang-include-cleaner/Types.h"
@@ -40,6 +41,7 @@
#include "clang/AST/Type.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Specifiers.h"
@@ -160,14 +162,14 @@ const char *getMarkdownLanguage(const ASTContext &Ctx) {
return LangOpts.ObjC ? "objective-c" : "cpp";
}
-HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
- const PrintingPolicy &PP) {
+SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
+ const PrintingPolicy &PP) {
// TypePrinter doesn't resolve decltypes, so resolve them here.
// FIXME: This doesn't handle composite types that contain a decltype in them.
// We should rather have a printing policy for that.
while (!QT.isNull() && QT->isDecltypeType())
QT = QT->castAs<DecltypeType>()->getUnderlyingType();
- HoverInfo::PrintedType Result;
+ SymbolPrintedType Result;
llvm::raw_string_ostream OS(Result.Type);
// Special case: if the outer type is a tag type without qualifiers, then
// include the tag for extra clarity.
@@ -189,16 +191,16 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
return Result;
}
-HoverInfo::PrintedType printType(const TemplateTypeParmDecl *TTP) {
- HoverInfo::PrintedType Result;
+SymbolPrintedType printType(const TemplateTypeParmDecl *TTP) {
+ SymbolPrintedType Result;
Result.Type = TTP->wasDeclaredWithTypename() ? "typename" : "class";
if (TTP->isParameterPack())
Result.Type += "...";
return Result;
}
-HoverInfo::PrintedType printType(const NonTypeTemplateParmDecl *NTTP,
- const PrintingPolicy &PP) {
+SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
+ const PrintingPolicy &PP) {
auto PrintedType = printType(NTTP->getType(), NTTP->getASTContext(), PP);
if (NTTP->isParameterPack()) {
PrintedType.Type += "...";
@@ -208,9 +210,9 @@ HoverInfo::PrintedType printType(const NonTypeTemplateParmDecl *NTTP,
return PrintedType;
}
-HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
- const PrintingPolicy &PP) {
- HoverInfo::PrintedType Result;
+SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
+ const PrintingPolicy &PP) {
+ SymbolPrintedType Result;
llvm::raw_string_ostream OS(Result.Type);
OS << "template <";
llvm::StringRef Sep = "";
@@ -230,14 +232,14 @@ HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
return Result;
}
-std::vector<HoverInfo::Param>
+std::vector<SymbolParam>
fetchTemplateParameters(const TemplateParameterList *Params,
const PrintingPolicy &PP) {
assert(Params);
- std::vector<HoverInfo::Param> TempParameters;
+ std::vector<SymbolParam> TempParameters;
for (const Decl *Param : *Params) {
- HoverInfo::Param P;
+ SymbolParam P;
if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) {
P.Type = printType(TTP);
@@ -351,41 +353,13 @@ void enhanceFromIndex(HoverInfo &Hover, const NamedDecl &ND,
});
}
-// Default argument might exist but be unavailable, in the case of unparsed
-// arguments for example. This function returns the default argument if it is
-// available.
-const Expr *getDefaultArg(const ParmVarDecl *PVD) {
- // Default argument can be unparsed or uninstantiated. For the former we
- // can't do much, as token information is only stored in Sema and not
- // attached to the AST node. For the latter though, it is safe to proceed as
- // the expression is still valid.
- if (!PVD->hasDefaultArg() || PVD->hasUnparsedDefaultArg())
- return nullptr;
- return PVD->hasUninstantiatedDefaultArg() ? PVD->getUninstantiatedDefaultArg()
- : PVD->getDefaultArg();
-}
-
-HoverInfo::Param toHoverInfoParam(const ParmVarDecl *PVD,
- const PrintingPolicy &PP) {
- HoverInfo::Param Out;
- Out.Type = printType(PVD->getType(), PVD->getASTContext(), PP);
- if (!PVD->getName().empty())
- Out.Name = PVD->getNameAsString();
- if (const Expr *DefArg = getDefaultArg(PVD)) {
- Out.Default.emplace();
- llvm::raw_string_ostream OS(*Out.Default);
- DefArg->printPretty(OS, nullptr, PP);
- }
- return Out;
-}
-
// Populates Type, ReturnType, and Parameters for function-like decls.
void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D,
const FunctionDecl *FD,
const PrintingPolicy &PP) {
HI.Parameters.emplace();
for (const ParmVarDecl *PVD : FD->parameters())
- HI.Parameters->emplace_back(toHoverInfoParam(PVD, PP));
+ HI.Parameters->emplace_back(createSymbolParam(PVD, PP));
// We don't want any type info, if name already contains it. This is true for
// constructors/destructors and conversion operators.
@@ -626,6 +600,9 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP,
HI.Name = printName(Ctx, *D);
const auto *CommentD = getDeclForComment(D);
HI.Documentation = getDeclComment(Ctx, *CommentD);
+ // safe the language options to be able to create the comment::CommandTraits
+ // to parse the documentation
+ HI.CommentOpts = D->getASTContext().getLangOpts().CommentOpts;
enhanceFromIndex(HI, *CommentD, Index);
if (HI.Documentation.empty())
HI.Documentation = synthesizeDocumentation(D);
@@ -812,7 +789,7 @@ HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok,
return HI;
}
-std::string typeAsDefinition(const HoverInfo::PrintedType &PType) {
+std::string typeAsDefinition(const SymbolPrintedType &PType) {
std::string Result;
llvm::raw_string_ostream OS(Result);
OS << PType.Type;
@@ -1095,7 +1072,7 @@ void maybeAddCalleeArgInfo(const SelectionTree::Node *N, HoverInfo &HI,
// Extract matching argument from function declaration.
if (const ParmVarDecl *PVD = Parameters[I]) {
- HI.CalleeArgInfo.emplace(toHoverInfoParam(PVD, PP));
+ HI.CalleeArgInfo.emplace(createSymbolParam(PVD, PP));
if (N == &OuterNode)
PassType.PassBy = getPassMode(PVD->getType());
}
@@ -1455,30 +1432,38 @@ markup::Document HoverInfo::present() const {
// Put a linebreak after header to increase readability.
Output.addRuler();
- // Print Types on their own lines to reduce chances of getting line-wrapped by
- // editor, as they might be long.
- if (ReturnType) {
- // For functions we display signature in a list form, e.g.:
- // → `x`
- // Parameters:
- // - `bool param1`
- // - `int param2 = 5`
- Output.addParagraph().appendText("→ ").appendCode(
- llvm::to_string(*ReturnType));
- }
- if (Parameters && !Parameters->empty()) {
- Output.addParagraph().appendText("Parameters: ");
- markup::BulletList &L = Output.addBulletList();
- for (const auto &Param : *Parameters)
- L.addItem().addParagraph().appendCode(llvm::to_string(Param));
- }
+ if (!Documentation.empty()) {
+ llvm::BumpPtrAllocator Allocator;
+ comments::CommandTraits Traits(Allocator, CommentOpts);
+ docCommentToMarkup(Output, Documentation, Allocator, Traits, Type,
+ ReturnType, Parameters);
+ } else {
+ // Print Types on their own lines to reduce chances of getting line-wrapped
+ // by editor, as they might be long.
+ if (ReturnType) {
+ // For functions we display signature in a list form, e.g.:
+ // → `x`
+ // Parameters:
+ // - `bool param1`
+ // - `int param2 = 5`
+ Output.addParagraph().appendText("→ ").appendCode(
+ llvm::to_string(*ReturnType));
+ }
- // Don't print Type after Parameters or ReturnType as this will just duplicate
- // the information
- if (Type && !ReturnType && !Parameters)
- Output.addParagraph().appendText("Type: ").appendCode(
- llvm::to_string(*Type));
+ if (Parameters && !Parameters->empty()) {
+ Output.addParagraph().appendText("Parameters: ");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto &Param : *Parameters)
+ L.addItem().addParagraph().appendCode(llvm::to_string(Param));
+ }
+
+ // Don't print Type after Parameters or ReturnType as this will just
+ // duplicate the information
+ if (Type && !ReturnType && !Parameters)
+ Output.addParagraph().appendText("Type: ").appendCode(
+ llvm::to_string(*Type));
+ }
if (Value) {
markup::Paragraph &P = Output.addParagraph();
@@ -1518,9 +1503,6 @@ markup::Document HoverInfo::present() const {
Output.addParagraph().appendText(OS.str());
}
- if (!Documentation.empty())
- parseDocumentation(Documentation, Output);
-
if (!Definition.empty()) {
Output.addRuler();
std::string Buffer;
@@ -1646,26 +1628,5 @@ void parseDocumentation(llvm::StringRef Input, markup::Document &Output) {
FlushParagraph();
}
-llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
- const HoverInfo::PrintedType &T) {
- OS << T.Type;
- if (T.AKA)
- OS << " (aka " << *T.AKA << ")";
- return OS;
-}
-
-llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
- const HoverInfo::Param &P) {
- if (P.Type)
- OS << P.Type->Type;
- if (P.Name)
- OS << " " << *P.Name;
- if (P.Default)
- OS << " = " << *P.Default;
- if (P.Type && P.Type->AKA)
- OS << " (aka " << *P.Type->AKA << ")";
- return OS;
-}
-
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index fe689de44732e..f485f02f3d516 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -11,6 +11,7 @@
#include "ParsedAST.h"
#include "Protocol.h"
+#include "SymbolDocumentation.h"
#include "support/Markup.h"
#include "clang/Index/IndexSymbol.h"
#include <optional>
@@ -25,33 +26,6 @@ namespace clangd {
/// embedding clients can use the structured information to provide their own
/// UI.
struct HoverInfo {
- /// Contains pretty-printed type and desugared type
- struct PrintedType {
- PrintedType() = default;
- PrintedType(const char *Type) : Type(Type) {}
- PrintedType(const char *Type, const char *AKAType)
- : Type(Type), AKA(AKAType) {}
-
- /// Pretty-printed type
- std::string Type;
- /// Desugared type
- std::optional<std::string> AKA;
- };
-
- /// Represents parameters of a function, a template or a macro.
- /// For example:
- /// - void foo(ParamType Name = DefaultValue)
- /// - #define FOO(Name)
- /// - template <ParamType Name = DefaultType> class Foo {};
- struct Param {
- /// The printable parameter type, e.g. "int", or "typename" (in
- /// TemplateParameters), might be std::nullopt for macro parameters.
- std::optional<PrintedType> Type;
- /// std::nullopt for unnamed parameters.
- std::optional<std::string> Name;
- /// std::nullopt if no default is provided.
- std::optional<std::string> Default;
- };
/// For a variable named Bar, declared in clang::clangd::Foo::getFoo the
/// following fields will hold:
@@ -74,6 +48,8 @@ struct HoverInfo {
std::optional<Range> SymRange;
index::SymbolKind Kind = index::SymbolKind::Unknown;
std::string Documentation;
+ // required to create a comments::CommandTraits object without the ASTContext
+ CommentOptions CommentOpts;
/// Source code containing the definition of the symbol.
std::string Definition;
const char *DefinitionLanguage = "cpp";
@@ -82,13 +58,13 @@ struct HoverInfo {
std::string AccessSpecifier;
/// Printable variable type.
/// Set only for variables.
- std::optional<PrintedType> Type;
+ std::optional<SymbolPrintedType> Type;
/// Set for functions and lambdas.
- std::optional<PrintedType> ReturnType;
+ std::optional<SymbolPrintedType> ReturnType;
/// Set for functions, lambdas and macros with parameters.
- std::optional<std::vector<Param>> Parameters;
+ std::optional<std::vector<SymbolParam>> Parameters;
/// Set for all templates(function, class, variable).
- std::optional<std::vector<Param>> TemplateParameters;
+ std::optional<std::vector<SymbolParam>> TemplateParameters;
/// Contains the evaluated value of the symbol if available.
std::optional<std::string> Value;
/// Contains the bit-size of fields and types where it's interesting.
@@ -101,7 +77,7 @@ struct HoverInfo {
std::optional<uint64_t> Align;
// Set when symbol is inside function call. Contains information extracted
// from the callee definition about the argument this is passed as.
- std::optional<Param> CalleeArgInfo;
+ std::optional<SymbolParam> CalleeArgInfo;
struct PassType {
// How the variable is passed to callee.
enum PassMode { Ref, ConstRef, Value };
@@ -122,11 +98,6 @@ struct HoverInfo {
markup::Document present() const;
};
-inline bool operator==(const HoverInfo::PrintedType &LHS,
- const HoverInfo::PrintedType &RHS) {
- return std::tie(LHS.Type, LHS.AKA) == std::tie(RHS.Type, RHS.AKA);
-}
-
inline bool operator==(const HoverInfo::PassType &LHS,
const HoverInfo::PassType &RHS) {
return std::tie(LHS.PassBy, LHS.Converted) ==
@@ -137,15 +108,6 @@ inline bool operator==(const HoverInfo::PassType &LHS,
// FIXME: move to another file so CodeComplete doesn't depend on Hover.
void parseDocumentation(llvm::StringRef Input, markup::Document &Output);
-llvm::raw_ostream &operator<<(llvm::raw_ostream &,
- const HoverInfo::PrintedType &);
-llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
-inline bool operator==(const HoverInfo::Param &LHS,
- const HoverInfo::Param &RHS) {
- return std::tie(LHS.Type, LHS.Name, LHS.Default) ==
- std::tie(RHS.Type, RHS.Name, RHS.Default);
-}
-
/// Get the hover information when hovering at \p Pos.
std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
const format::FormatStyle &Style,
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp
new file mode 100644
index 0000000000000..771f1d99e639e
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -0,0 +1,503 @@
+//===--- SymbolDocumentation.cpp ==-------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SymbolDocumentation.h"
+#include "Config.h"
+#include "support/Markup.h"
+#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentVisitor.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ScopedPrinter.h"
+
+namespace clang {
+namespace clangd {
+
+void commandToMarkup(markup::Paragraph &Out, StringRef Command,
+ comments::CommandMarkerKind CommandMarker,
+ StringRef Args) {
+ Out.appendBoldText(
+ (CommandMarker == (comments::CommandMarkerKind::CMK_At) ? "@" : "\\") +
+ Command.str());
+ if (!Args.empty()) {
+ Out.appendSpace();
+ Out.appendEmphasizedText(Args.str());
+ }
+}
+
+class ParagraphToMarkupDocument
+ : public comments::ConstCommentVisitor<ParagraphToMarkupDocument> {
+public:
+ ParagraphToMarkupDocument(markup::Paragraph &Out,
+ const comments::CommandTraits &Traits)
+ : Out(Out), Traits(Traits) {}
+
+ void visitParagraphComment(const comments::ParagraphComment *C) {
+ for (const auto *Child = C->child_begin(); Child != C->child_end();
+ ++Child) {
+ visit(*Child);
+ }
+ }
+
+ void visitTextComment(const comments::TextComment *C) {
+ Out.appendText(C->getText().str());
+ // A paragraph may have multiple TextComments seperated by a newline.
+ // We need to add a space to separate them in the markup::Document.
+ if (C->hasTrailingNewline())
+ Out.appendSpace();
+ }
+
+ void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+
+ if (C->getNumArgs() > 0) {
+ std::string ArgText;
+ for (unsigned I = 0; I < C->getNumArgs(); ++I) {
+ if (!ArgText.empty())
+ ArgText += " ";
+ ArgText += C->getArgText(I);
+ }
+
+ switch (C->getRenderKind()) {
+ case comments::InlineCommandRenderKind::Monospaced:
+ Out.appendCode(ArgText);
+ break;
+ case comments::InlineCommandRenderKind::Bold:
+ Out.appendBoldText(ArgText);
+ break;
+ case comments::InlineCommandRenderKind::Emphasized:
+ Out.appendEmphasizedText(ArgText);
+ break;
+ default:
+ commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
+ ArgText);
+ break;
+ }
+ } else {
+ commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
+ "");
+ }
+ }
+
+private:
+ markup::Paragraph &Out;
+ const comments::CommandTraits &Traits;
+};
+
+class BlockCommentToMarkupDocument
+ : public comments::ConstCommentVisitor<BlockCommentToMarkupDocument> {
+public:
+ BlockCommentToMarkupDocument(markup::Document &Out,
+ const comments::CommandTraits &Traits)
+ : Out(Out), Traits(Traits) {}
+
+ void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+ // Some commands have arguments, like \throws.
+ // The arguments are not part of the paragraph.
+ // We need reconstruct them here.
+ std::string ArgText;
+ for (unsigned I = 0; I < B->getNumArgs(); ++I) {
+ if (!ArgText.empty())
+ ArgText += " ";
+ ArgText += B->getArgText(I);
+ }
+
+ auto &P = Out.addParagraph();
+
+ commandToMarkup(P, B->getCommandName(Traits), B->getCommandMarker(),
+ ArgText);
+
+ if (!B->getParagraph()->isWhitespace()) {
+ P.appendSpace();
+ ParagraphToMarkupDocument(P, Traits).visit(B->getParagraph());
+ }
+ }
+
+private:
+ markup::Document &Out;
+ const comments::CommandTraits &Traits;
+};
+
+class FullCommentToMarkupDocument
+ : public comments::ConstCommentVisitor<FullCommentToMarkupDocument> {
+public:
+ FullCommentToMarkupDocument(
+ const comments::FullComment &FC, const comments::CommandTraits &Traits,
+ markup::Document &Doc, std::optional<SymbolPrintedType> SymbolType,
+ std::optional<SymbolPrintedType> SymbolReturnType,
+ const std::optional<std::vector<SymbolParam>> &SymbolParameters)
+ : Traits(Traits), Output(Doc) {
+
+ for (auto *Block : FC.getBlocks()) {
+ visit(Block);
+ }
+
+ for (const auto *BP : BriefParagraphs)
+ ParagraphToMarkupDocument(Output.addParagraph(), Traits).visit(BP);
+
+ if (!BriefParagraphs.empty())
+ Output.addRuler();
+
+ if (SymbolReturnType.has_value()) {
+ std::string RT = llvm::to_string(*SymbolReturnType);
+ if (!RT.empty()) {
+ auto &P = Output.addParagraph().appendText("→ ").appendCode(RT);
+ if (!ReturnParagraphs.empty()) {
+ P.appendText(": ");
+ for (const auto *RP : ReturnParagraphs)
+ ParagraphToMarkupDocument(P, Traits).visit(RP);
+ }
+ }
+ }
+
+ if (SymbolParameters.has_value() && !SymbolParameters->empty()) {
+ Output.addParagraph().appendText("Parameters:");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto &P : *SymbolParameters) {
+ markup::Paragraph &PP =
+ L.addItem().addParagraph().appendCode(llvm::to_string(P));
+
+ if (!P.Name.has_value())
+ continue;
+
+ if (const auto *PCC = Parameters[*P.Name]) {
+ PP.appendText(": ");
+ ParagraphToMarkupDocument(PP, Traits).visit(PCC->getParagraph());
+ Parameters.erase(*P.Name);
+ }
+ }
+ }
+
+ // Don't print Type after Parameters or ReturnType as this will just
+ // duplicate the information
+ if (SymbolType.has_value() && !SymbolReturnType.has_value() &&
+ (!SymbolParameters.has_value() || SymbolParameters->empty())) {
+ Output.addParagraph().appendText("Type: ").appendCode(
+ llvm::to_string(*SymbolType));
+ }
+
+ if (!WarningParagraphs.empty()) {
+ Output.addParagraph().appendText("Warning").appendText(
+ WarningParagraphs.size() > 1 ? "s:" : ":");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto *WP : WarningParagraphs)
+ ParagraphToMarkupDocument(L.addItem().addParagraph(), Traits).visit(WP);
+ Output.addRuler();
+ }
+
+ if (!NoteParagraphs.empty()) {
+ if (WarningParagraphs.empty())
+ Output.addRuler();
+ Output.addParagraph().appendText("Note").appendText(
+ WarningParagraphs.size() > 1 ? "s:" : ":");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto *WP : NoteParagraphs)
+ ParagraphToMarkupDocument(L.addItem().addParagraph(), Traits).visit(WP);
+ Output.addRuler();
+ }
+
+ for (unsigned I = 0; I < CommentPartIndex; ++I) {
+ if (const auto *UnhandledCommand = UnhandledCommands.lookup(I)) {
+ BlockCommentToMarkupDocument(Output, Traits).visit(UnhandledCommand);
+ continue;
+ }
+ if (const auto *FreeText = FreeParagraphs.lookup(I)) {
+ ParagraphToMarkupDocument(Output.addParagraph(), Traits)
+ .visit(FreeText);
+ continue;
+ }
+ }
+ }
+
+ void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+ const comments::CommandInfo *Info =
+ Traits.getCommandInfo(B->getCommandID());
+
+ // Visit B->getParagraph() for commands that we have special fields for,
+ // so that the command name won't be included in the string.
+ // Otherwise, we want to keep the command name, so visit B itself.
+ if (Info->IsBriefCommand) {
+ BriefParagraphs.push_back(B->getParagraph());
+ } else if (Info->IsReturnsCommand) {
+ ReturnParagraphs.push_back(B->getParagraph());
+ } else {
+ const llvm::StringRef CommandName = B->getCommandName(Traits);
+ if (CommandName == "warning") {
+ WarningParagraphs.push_back(B->getParagraph());
+ } else if (CommandName == "note") {
+ NoteParagraphs.push_back(B->getParagraph());
+ } else {
+ UnhandledCommands[CommentPartIndex] = B;
+ }
+ CommentPartIndex++;
+ }
+ }
+
+ void visitParagraphComment(const comments::ParagraphComment *P) {
+ FreeParagraphs[CommentPartIndex] = P;
+ CommentPartIndex++;
+ }
+
+ void visitParamCommandComment(const comments::ParamCommandComment *P) {
+ Parameters[P->getParamNameAsWritten()] = P;
+ }
+
+private:
+ const comments::CommandTraits &Traits;
+ markup::Document &Output;
+
+ unsigned CommentPartIndex = 0;
+
+ /// Paragraph of the "brief" command.
+ llvm::SmallVector<const comments::ParagraphComment *, 1> BriefParagraphs;
+
+ /// Paragraph of the "return" command.
+ llvm::SmallVector<const comments::ParagraphComment *, 1> ReturnParagraphs;
+
+ /// Paragraph(s) of the "note" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> NoteParagraphs;
+
+ /// Paragraph(s) of the "warning" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> WarningParagraphs;
+
+ /// Parsed paragaph(s) of the "param" comamnd(s)
+ llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *>
+ Parameters;
+
+ /// All the paragraphs we don't have any special handling for,
+ /// e.g. "details".
+ llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
+ UnhandledCommands;
+
+ /// All free text paragraphs.
+ llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *>
+ FreeParagraphs;
+};
+
+void fullCommentToMarkupDocument(
+ markup::Document &Doc, const comments::FullComment *FC,
+ const comments::CommandTraits &Traits,
+ std::optional<SymbolPrintedType> SymbolType,
+ std::optional<SymbolPrintedType> SymbolReturnType,
+ const std::optional<std::vector<SymbolParam>> &SymbolParameters) {
+ if (!FC)
+ return;
+ FullCommentToMarkupDocument(*FC, Traits, Doc, SymbolType, SymbolReturnType,
+ SymbolParameters);
+}
+
+class ParamDocStringVisitor
+ : public comments::ConstCommentVisitor<ParamDocStringVisitor> {
+public:
+ ParamDocStringVisitor(const comments::FullComment *FC, StringRef ParamName,
+ const comments::CommandTraits &Traits)
+ : ParamName(ParamName), Traits(Traits) {
+ if (!FC)
+ return;
+
+ for (const auto *Block : FC->getBlocks()) {
+ visit(Block);
+ if (ParamCommentFound)
+ break;
+ }
+ }
+
+ void visitParamCommandComment(const comments::ParamCommandComment *P) {
+ if (P->getParamNameAsWritten() == ParamName) {
+ ParamCommentFound = true;
+ visit(P->getParagraph());
+ }
+ }
+
+ void visitParagraphComment(const comments::ParagraphComment *C) {
+ if (!ParamCommentFound)
+ return;
+ for (const auto *Child = C->child_begin(); Child != C->child_end();
+ ++Child) {
+ visit(*Child);
+ }
+ }
+
+ void visitTextComment(const comments::TextComment *C) {
+ if (!ParamCommentFound)
+ return;
+ if (Result.empty())
+ // When hovering over the parameter, the documentation is parsed the same
+ // way as any other comment. This means that the brief command is shown
+ // more prominently as the paragraph in the hover.
+ // Hence we add the brief command to the beginning of the documentation to
+ // reuse the same behaviour.
+ Result = "\\brief";
+
+ Result += " " + C->getText().trim().str();
+ }
+
+ void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+ if (!ParamCommentFound)
+ return;
+
+ if (Result.empty())
+ // When hovering over the parameter, the documentation is parsed the same
+ // way as any other comment. This means that the brief command is shown
+ // more prominently as the paragraph in the hover.
+ // Hence we add the brief command to the beginning of the documentation to
+ // reuse the same behaviour.
+ Result = "\\brief";
+
+ Result += " ";
+ Result += C->getCommandMarker() == (comments::CommandMarkerKind::CMK_At)
+ ? "@"
+ : "\\" + C->getCommandName(Traits).str();
+
+ if (C->getNumArgs() > 0) {
+ for (unsigned I = 0; I < C->getNumArgs(); ++I) {
+ Result += " " + C->getArgText(I).str();
+ }
+ }
+ }
+
+ StringRef getParamDocString() const { return Result; }
+
+private:
+ StringRef ParamName;
+ std::string Result;
+ bool ParamCommentFound = false;
+ const comments::CommandTraits &Traits;
+};
+
+std::string getParamDocString(const comments::FullComment *FC,
+ StringRef ParamName,
+ const comments::CommandTraits &Traits) {
+ if (!FC)
+ return "";
+ ParamDocStringVisitor PDSV(FC, ParamName, Traits);
+ return PDSV.getParamDocString().str();
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ const SymbolPrintedType &T) {
+ OS << T.Type;
+ if (T.AKA)
+ OS << " (aka " << *T.AKA << ")";
+ return OS;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolParam &P) {
+ if (P.Type)
+ OS << P.Type->Type;
+ if (P.Name)
+ OS << " " << *P.Name;
+ if (P.Default)
+ OS << " = " << *P.Default;
+ if (P.Type && P.Type->AKA)
+ OS << " (aka " << *P.Type->AKA << ")";
+ return OS;
+}
+
+SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
+ const PrintingPolicy &PP) {
+ // TypePrinter doesn't resolve decltypes, so resolve them here.
+ // FIXME: This doesn't handle composite types that contain a decltype in them.
+ // We should rather have a printing policy for that.
+ while (!QT.isNull() && QT->isDecltypeType())
+ QT = QT->castAs<DecltypeType>()->getUnderlyingType();
+ SymbolPrintedType Result;
+ llvm::raw_string_ostream OS(Result.Type);
+ // Special case: if the outer type is a tag type without qualifiers, then
+ // include the tag for extra clarity.
+ // This isn't very idiomatic, so don't attempt it for complex cases, including
+ // pointers/references, template specializations, etc.
+ if (!QT.isNull() && !QT.hasQualifiers() && PP.SuppressTagKeyword) {
+ if (auto *TT = llvm::dyn_cast<TagType>(QT.getTypePtr()))
+ OS << TT->getDecl()->getKindName() << " ";
+ }
+ QT.print(OS, PP);
+
+ const Config &Cfg = Config::current();
+ if (!QT.isNull() && Cfg.Hover.ShowAKA) {
+ bool ShouldAKA = false;
+ QualType DesugaredTy = clang::desugarForDiagnostic(ASTCtx, QT, ShouldAKA);
+ if (ShouldAKA)
+ Result.AKA = DesugaredTy.getAsString(PP);
+ }
+ return Result;
+}
+
+SymbolPrintedType printType(const TemplateTypeParmDecl *TTP) {
+ SymbolPrintedType Result;
+ Result.Type = TTP->wasDeclaredWithTypename() ? "typename" : "class";
+ if (TTP->isParameterPack())
+ Result.Type += "...";
+ return Result;
+}
+
+SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
+ const PrintingPolicy &PP) {
+ auto PrintedType = printType(NTTP->getType(), NTTP->getASTContext(), PP);
+ if (NTTP->isParameterPack()) {
+ PrintedType.Type += "...";
+ if (PrintedType.AKA)
+ *PrintedType.AKA += "...";
+ }
+ return PrintedType;
+}
+
+SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
+ const PrintingPolicy &PP) {
+ SymbolPrintedType Result;
+ llvm::raw_string_ostream OS(Result.Type);
+ OS << "template <";
+ llvm::StringRef Sep = "";
+ for (const Decl *Param : *TTP->getTemplateParameters()) {
+ OS << Sep;
+ Sep = ", ";
+ if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param))
+ OS << printType(TTP).Type;
+ else if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param))
+ OS << printType(NTTP, PP).Type;
+ else if (const auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(Param))
+ OS << printType(TTPD, PP).Type;
+ }
+ // FIXME: TemplateTemplateParameter doesn't store the info on whether this
+ // param was a "typename" or "class".
+ OS << "> class";
+ return Result;
+}
+
+// Default argument might exist but be unavailable, in the case of unparsed
+// arguments for example. This function returns the default argument if it is
+// available.
+const Expr *getDefaultArg(const ParmVarDecl *PVD) {
+ // Default argument can be unparsed or uninstantiated. For the former we
+ // can't do much, as token information is only stored in Sema and not
+ // attached to the AST node. For the latter though, it is safe to proceed as
+ // the expression is still valid.
+ if (!PVD->hasDefaultArg() || PVD->hasUnparsedDefaultArg())
+ return nullptr;
+ return PVD->hasUninstantiatedDefaultArg() ? PVD->getUninstantiatedDefaultArg()
+ : PVD->getDefaultArg();
+}
+
+SymbolParam createSymbolParam(const ParmVarDecl *PVD,
+ const PrintingPolicy &PP) {
+ SymbolParam Out;
+ Out.Type = printType(PVD->getType(), PVD->getASTContext(), PP);
+ if (!PVD->getName().empty())
+ Out.Name = PVD->getNameAsString();
+ if (const Expr *DefArg = getDefaultArg(PVD)) {
+ Out.Default.emplace();
+ llvm::raw_string_ostream OS(*Out.Default);
+ DefArg->printPretty(OS, nullptr, PP);
+ }
+ return Out;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h
new file mode 100644
index 0000000000000..7da2fffb51e05
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -0,0 +1,92 @@
+//===--- SymbolDocumentation.h ==---------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Class to parse doxygen comments into a flat structure for consumption
+// in e.g. Hover and Code Completion
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
+
+#include "support/Markup.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclTemplate.h"
+#include "llvm/Support/raw_ostream.h"
+#include <optional>
+#include <string>
+#include <tuple>
+
+namespace clang {
+namespace clangd {
+
+/// Contains pretty-printed type and desugared type
+struct SymbolPrintedType {
+ SymbolPrintedType() = default;
+ SymbolPrintedType(const char *Type) : Type(Type) {}
+ SymbolPrintedType(const char *Type, const char *AKAType)
+ : Type(Type), AKA(AKAType) {}
+
+ /// Pretty-printed type
+ std::string Type;
+ /// Desugared type
+ std::optional<std::string> AKA;
+};
+
+/// Represents parameters of a function, a template or a macro.
+/// For example:
+/// - void foo(ParamType Name = DefaultValue)
+/// - #define FOO(Name)
+/// - template <ParamType Name = DefaultType> class Foo {};
+struct SymbolParam {
+ /// The printable parameter type, e.g. "int", or "typename" (in
+ /// TemplateParameters), might be std::nullopt for macro parameters.
+ std::optional<SymbolPrintedType> Type;
+ /// std::nullopt for unnamed parameters.
+ std::optional<std::string> Name;
+ /// std::nullopt if no default is provided.
+ std::optional<std::string> Default;
+};
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ const SymbolPrintedType &T);
+inline bool operator==(const SymbolPrintedType &LHS,
+ const SymbolPrintedType &RHS) {
+ return std::tie(LHS.Type, LHS.AKA) == std::tie(RHS.Type, RHS.AKA);
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolParam &);
+inline bool operator==(const SymbolParam &LHS, const SymbolParam &RHS) {
+ return std::tie(LHS.Type, LHS.Name, LHS.Default) ==
+ std::tie(RHS.Type, RHS.Name, RHS.Default);
+}
+
+SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
+ const PrintingPolicy &PP);
+SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
+ const PrintingPolicy &PP);
+SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
+ const PrintingPolicy &PP);
+
+SymbolParam createSymbolParam(const ParmVarDecl *PVD, const PrintingPolicy &PP);
+
+void fullCommentToMarkupDocument(
+ markup::Document &Doc, const comments::FullComment *FC,
+ const comments::CommandTraits &Traits,
+ std::optional<SymbolPrintedType> SymbolType,
+ std::optional<SymbolPrintedType> SymbolReturnType,
+ const std::optional<std::vector<SymbolParam>> &SymbolParameters);
+
+std::string getParamDocString(const comments::FullComment *FC,
+ StringRef ParamName,
+ const comments::CommandTraits &Traits);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
diff --git a/clang-tools-extra/clangd/support/Markup.cpp b/clang-tools-extra/clangd/support/Markup.cpp
index 63aff96b02056..d025f078c6d8d 100644
--- a/clang-tools-extra/clangd/support/Markup.cpp
+++ b/clang-tools-extra/clangd/support/Markup.cpp
@@ -354,6 +354,12 @@ void Paragraph::renderMarkdown(llvm::raw_ostream &OS) const {
case Chunk::InlineCode:
OS << renderInlineBlock(C.Contents);
break;
+ case Chunk::Bold:
+ OS << "**" << renderText(C.Contents, !HasChunks) << "**";
+ break;
+ case Chunk::Emphasized:
+ OS << "*" << renderText(C.Contents, !HasChunks) << "*";
+ break;
}
HasChunks = true;
NeedsSpace = C.SpaceAfter;
@@ -387,6 +393,10 @@ void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
llvm::StringRef Marker = "";
if (C.Preserve && C.Kind == Chunk::InlineCode)
Marker = chooseMarker({"`", "'", "\""}, C.Contents);
+ else if (C.Kind == Chunk::Bold)
+ Marker = "**";
+ else if (C.Kind == Chunk::Emphasized)
+ Marker = "*";
OS << Marker << C.Contents << Marker;
NeedsSpace = C.SpaceAfter;
}
@@ -433,6 +443,32 @@ Paragraph &Paragraph::appendText(llvm::StringRef Text) {
return *this;
}
+Paragraph &Paragraph::appendEmphasizedText(llvm::StringRef Text) {
+ std::string Norm = canonicalizeSpaces(Text);
+ if (Norm.empty())
+ return *this;
+ Chunks.emplace_back();
+ Chunk &C = Chunks.back();
+ C.Contents = std::move(Norm);
+ C.Kind = Chunk::Emphasized;
+ C.SpaceBefore = llvm::isSpace(Text.front());
+ C.SpaceAfter = llvm::isSpace(Text.back());
+ return *this;
+}
+
+Paragraph &Paragraph::appendBoldText(llvm::StringRef Text) {
+ std::string Norm = canonicalizeSpaces(Text);
+ if (Norm.empty())
+ return *this;
+ Chunks.emplace_back();
+ Chunk &C = Chunks.back();
+ C.Contents = std::move(Norm);
+ C.Kind = Chunk::Bold;
+ C.SpaceBefore = llvm::isSpace(Text.front());
+ C.SpaceAfter = llvm::isSpace(Text.back());
+ return *this;
+}
+
Paragraph &Paragraph::appendCode(llvm::StringRef Code, bool Preserve) {
bool AdjacentCode =
!Chunks.empty() && Chunks.back().Kind == Chunk::InlineCode;
diff --git a/clang-tools-extra/clangd/support/Markup.h b/clang-tools-extra/clangd/support/Markup.h
index 3a869c49a2cbb..b573539b65912 100644
--- a/clang-tools-extra/clangd/support/Markup.h
+++ b/clang-tools-extra/clangd/support/Markup.h
@@ -49,6 +49,12 @@ class Paragraph : public Block {
/// Append plain text to the end of the string.
Paragraph &appendText(llvm::StringRef Text);
+ /// Append emphasized text, this translates to the * block in markdown.
+ Paragraph &appendEmphasizedText(llvm::StringRef Text);
+
+ /// Append bold text, this translates to the ** block in markdown.
+ Paragraph &appendBoldText(llvm::StringRef Text);
+
/// Append inline code, this translates to the ` block in markdown.
/// \p Preserve indicates the code span must be apparent even in plaintext.
Paragraph &appendCode(llvm::StringRef Code, bool Preserve = false);
@@ -59,10 +65,7 @@ class Paragraph : public Block {
private:
struct Chunk {
- enum {
- PlainText,
- InlineCode,
- } Kind = PlainText;
+ enum { PlainText, InlineCode, Bold, Emphasized } Kind = PlainText;
// Preserve chunk markers in plaintext.
bool Preserve = false;
std::string Contents;
diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
index de5f533d31645..81a14e069bd82 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
@@ -11,6 +11,7 @@
#include "clang/Sema/CodeCompleteConsumer.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <optional>
namespace clang {
namespace clangd {
@@ -236,6 +237,224 @@ TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) {
EXPECT_EQ(Snippet, "${1:(type2)}");
}
+TEST(CompletionString, ParseDocumentation) {
+
+ llvm::BumpPtrAllocator Allocator;
+ CommentOptions CommentOpts;
+ comments::CommandTraits Traits(Allocator, CommentOpts);
+
+ struct Case {
+ llvm::StringRef Documentation;
+ std::optional<SymbolPrintedType> SymbolType;
+ std::optional<SymbolPrintedType> SymbolReturnType;
+ std::optional<std::vector<SymbolParam>> SymbolParameters;
+ llvm::StringRef ExpectedRenderMarkdown;
+ llvm::StringRef ExpectedRenderPlainText;
+ } Cases[] = {
+ {
+ "foo bar",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo bar",
+ "foo bar",
+ },
+ {
+ "foo\nbar\n",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo bar",
+ "foo bar",
+ },
+ {
+ "foo\n\nbar\n",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo \nbar",
+ "foo\nbar",
+ },
+ {
+ "foo \\p bar baz",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo `bar` baz",
+ "foo bar baz",
+ },
+ {
+ "foo \\e bar baz",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo *bar* baz",
+ "foo *bar* baz",
+ },
+ {
+ "foo \\b bar baz",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo **bar** baz",
+ "foo **bar** baz",
+ },
+ {
+ "foo \\ref bar baz",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo **\\\\ref** *bar* baz",
+ "foo **\\ref** *bar* baz",
+ },
+ {
+ "foo @ref bar baz",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "foo **@ref** *bar* baz",
+ "foo **@ref** *bar* baz",
+ },
+ {
+ "\\throw exception foo",
+ std::nullopt,
+ std::nullopt,
+ std::nullopt,
+ "**\\\\throw** *exception* foo",
+ "**\\throw** *exception* foo",
+ },
+ {"@throws exception foo\n\n at note bar\n\n at warning baz\n\n at details "
+ "qux\n\nfree standing paragraph",
+ std::nullopt, std::nullopt, std::nullopt,
+ "Warning: \n- baz\n\n---\n"
+ "Note: \n- bar\n\n---\n"
+ "**@throws** *exception* foo \n"
+ "**@details** qux \n"
+ "free standing paragraph",
+ "Warning:\n- baz\n\n"
+ "Note:\n- bar\n\n"
+ "**@throws** *exception* foo\n"
+ "**@details** qux\n"
+ "free standing paragraph"},
+ {"@throws exception foo\n\n at note bar\n\n at warning baz\n\n at note another "
+ "note\n\n at warning another warning\n\n at details qux\n\nfree standing "
+ "paragraph",
+ std::nullopt, std::nullopt, std::nullopt,
+ "Warnings: \n- baz\n- another warning\n\n---\n"
+ "Notes: \n- bar\n- another note\n\n---\n"
+ "**@throws** *exception* foo \n"
+ "**@details** qux \n"
+ "free standing paragraph",
+ "Warnings:\n- baz\n- another warning\n\n"
+ "Notes:\n- bar\n- another note\n\n"
+ "**@throws** *exception* foo\n"
+ "**@details** qux\n"
+ "free standing paragraph"},
+ {
+ "",
+ SymbolPrintedType("my_type", "type"),
+ std::nullopt,
+ std::nullopt,
+ "Type: `my_type (aka type)`",
+ "Type: my_type (aka type)",
+ },
+ {
+ "",
+ SymbolPrintedType("my_type", "type"),
+ SymbolPrintedType("my_ret_type", "type"),
+ std::nullopt,
+ "→ `my_ret_type (aka type)`",
+ "→ my_ret_type (aka type)",
+ },
+ {
+ "\\return foo",
+ SymbolPrintedType("my_type", "type"),
+ SymbolPrintedType("my_ret_type", "type"),
+ std::nullopt,
+ "→ `my_ret_type (aka type)`: foo",
+ "→ my_ret_type (aka type): foo",
+ },
+ {
+ "\\returns foo",
+ SymbolPrintedType("my_type", "type"),
+ SymbolPrintedType("my_ret_type", "type"),
+ std::nullopt,
+ "→ `my_ret_type (aka type)`: foo",
+ "→ my_ret_type (aka type): foo",
+ },
+ {
+ "",
+ std::nullopt,
+ std::nullopt,
+ std::vector<SymbolParam>{
+ {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+ "Parameters: \n- `my_type foo = default (aka type)`",
+ "Parameters:\n- my_type foo = default (aka type)",
+ },
+ {
+ "\\param foo bar",
+ std::nullopt,
+ std::nullopt,
+ std::vector<SymbolParam>{
+ {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+ "Parameters: \n- `my_type foo = default (aka type)`: bar",
+ "Parameters:\n- my_type foo = default (aka type): bar",
+ },
+ {
+ "\\param foo bar\n\n\\param baz qux",
+ std::nullopt,
+ std::nullopt,
+ std::vector<SymbolParam>{
+ {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+ "Parameters: \n- `my_type foo = default (aka type)`: bar",
+ "Parameters:\n- my_type foo = default (aka type): bar",
+ },
+ {
+ "\\brief This is a brief description\n\nlonger description "
+ "paragraph\n\n\\note foo\n\n\\warning warning\n\njust another "
+ "paragraph\\param foo bar\\return baz",
+ SymbolPrintedType("my_type", "type"),
+ SymbolPrintedType("my_ret_type", "type"),
+ std::vector<SymbolParam>{
+ {SymbolPrintedType("my_type", "type"), "foo", "default"}},
+ "This is a brief description \n\n"
+ "---\n"
+ "→ `my_ret_type (aka type)`: baz \n"
+ "Parameters: \n"
+ "- `my_type foo = default (aka type)`: bar\n\n"
+ "Warning: \n"
+ "- warning\n\n"
+ "---\n"
+ "Note: \n"
+ "- foo\n\n"
+ "---\n"
+ "longer description paragraph \n"
+ "just another paragraph",
+
+ R"(This is a brief description
+
+→ my_ret_type (aka type): baz
+Parameters:
+- my_type foo = default (aka type): bar
+Warning:
+- warning
+
+Note:
+- foo
+
+longer description paragraph
+just another paragraph)",
+ }};
+
+ for (const auto &C : Cases) {
+ markup::Document Doc;
+ docCommentToMarkup(Doc, C.Documentation, Allocator, Traits, C.SymbolType,
+ C.SymbolReturnType, C.SymbolParameters);
+ EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
+ EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 69f6df46c87ce..fdb3bfa0797b2 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -10,6 +10,7 @@
#include "Annotations.h"
#include "Config.h"
#include "Hover.h"
+#include "SymbolDocumentation.h"
#include "TestFS.h"
#include "TestIndex.h"
#include "TestTU.h"
@@ -639,7 +640,7 @@ class Foo final {})cpp";
[](HoverInfo &HI) {
HI.Name = "DECL_STR";
HI.Kind = index::SymbolKind::Macro;
- HI.Type = HoverInfo::PrintedType("const char *");
+ HI.Type = SymbolPrintedType("const char *");
HI.Definition = "#define DECL_STR(NAME, VALUE) const char *v_##NAME = "
"STRINGIFY(VALUE)\n\n"
"// Expands to\n"
@@ -1848,7 +1849,7 @@ TEST(Hover, All) {
HI.Type = "int ()";
HI.Definition = "int x()";
HI.ReturnType = "int";
- HI.Parameters = std::vector<HoverInfo::Param>{};
+ HI.Parameters = std::vector<SymbolParam>{};
}},
{
R"cpp(// Static method call
@@ -1865,7 +1866,7 @@ TEST(Hover, All) {
HI.Type = "int ()";
HI.Definition = "static int x()";
HI.ReturnType = "int";
- HI.Parameters = std::vector<HoverInfo::Param>{};
+ HI.Parameters = std::vector<SymbolParam>{};
}},
{
R"cpp(// Typedef
@@ -1944,7 +1945,7 @@ TEST(Hover, All) {
HI.Definition = "void foo()";
HI.Documentation = "";
HI.ReturnType = "void";
- HI.Parameters = std::vector<HoverInfo::Param>{};
+ HI.Parameters = std::vector<SymbolParam>{};
}},
{
R"cpp( // using declaration and two possible function declarations
@@ -2032,7 +2033,7 @@ TEST(Hover, All) {
HI.Definition = "void foo()";
HI.Documentation = "Function declaration";
HI.ReturnType = "void";
- HI.Parameters = std::vector<HoverInfo::Param>{};
+ HI.Parameters = std::vector<SymbolParam>{};
}},
{
R"cpp(// Enum declaration
@@ -2173,7 +2174,7 @@ TEST(Hover, All) {
HI.Definition = "template <> int foo<int>()";
HI.Documentation = "Templated function";
HI.ReturnType = "int";
- HI.Parameters = std::vector<HoverInfo::Param>{};
+ HI.Parameters = std::vector<SymbolParam>{};
// FIXME: We should populate template parameters with arguments in
// case of instantiations.
}},
@@ -2207,7 +2208,7 @@ TEST(Hover, All) {
HI.Type = "void ()";
HI.Definition = "void indexSymbol()";
HI.ReturnType = "void";
- HI.Parameters = std::vector<HoverInfo::Param>{};
+ HI.Parameters = std::vector<SymbolParam>{};
HI.Documentation = "comment from index";
}},
{
@@ -3440,8 +3441,8 @@ TEST(Hover, Present) {
},
R"(class foo
-Size: 10 bytes
documentation
+Size: 10 bytes
template <typename T, typename C = bool> class Foo {})",
},
@@ -3452,7 +3453,7 @@ template <typename T, typename C = bool> class Foo {})",
HI.Type = {"type", "c_type"};
HI.ReturnType = {"ret_type", "can_ret_type"};
HI.Parameters.emplace();
- HoverInfo::Param P;
+ SymbolParam P;
HI.Parameters->push_back(P);
P.Type = {"type", "can_type"};
HI.Parameters->push_back(P);
@@ -3955,7 +3956,7 @@ TEST(Hover, DisableShowAKA) {
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
ASSERT_TRUE(H);
- EXPECT_EQ(H->Type, HoverInfo::PrintedType("m_int"));
+ EXPECT_EQ(H->Type, SymbolPrintedType("m_int"));
}
TEST(Hover, HideBigInitializers) {
@@ -4093,7 +4094,7 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
/*Validator=*/
[](std::optional<HoverInfo> HI, size_t) {
EXPECT_EQ(HI->Value, "42 (0x2a)");
- EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int"));
+ EXPECT_EQ(HI->Type, SymbolPrintedType("int"));
},
},
{
@@ -4184,7 +4185,7 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
[](std::optional<HoverInfo> HI, size_t Id) {
switch (Id) {
case 0:
- EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int[10]"));
+ EXPECT_EQ(HI->Type, SymbolPrintedType("int[10]"));
break;
case 1:
case 2:
@@ -4210,11 +4211,11 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
switch (Id) {
case 0:
EXPECT_FALSE(HI->Value);
- EXPECT_EQ(HI->Type, HoverInfo::PrintedType("const (lambda)"));
+ EXPECT_EQ(HI->Type, SymbolPrintedType("const (lambda)"));
break;
case 1:
EXPECT_EQ(HI->Value, "0");
- EXPECT_EQ(HI->Type, HoverInfo::PrintedType("u64"));
+ EXPECT_EQ(HI->Type, SymbolPrintedType("u64"));
break;
case 2:
EXPECT_FALSE(HI);
@@ -4264,6 +4265,104 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
EXPECT_TRUE(H->Value);
EXPECT_TRUE(H->Type);
}
+
+TEST(Hover, FunctionParameters) {
+ struct {
+ const char *const Code;
+ const std::function<void(HoverInfo &)> ExpectedBuilder;
+ } Cases[] = {
+ {R"cpp(/// Function doc
+ void foo(int [[^a]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "";
+ }},
+ {R"cpp(/// Function doc
+ /// @param a this is doc for a
+ void foo(int [[^a]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "\\brief this is doc for a";
+ }},
+ {R"cpp(/// Function doc
+ /// @param b this is doc for b
+ void foo(int [[^a]], int b);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "";
+ }},
+ {R"cpp(/// Function doc
+ /// @param b this is doc for \p b
+ void foo(int a, int [[^b]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "b";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int b";
+ HI.Documentation = "\\brief this is doc for \\p b";
+ }},
+ };
+
+ // Create a tiny index, so tests above can verify documentation is fetched.
+ Symbol IndexSym = func("indexSymbol");
+ IndexSym.Documentation = "comment from index";
+ SymbolSlab::Builder Symbols;
+ Symbols.insert(IndexSym);
+ auto Index =
+ MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
+
+ for (const auto &Case : Cases) {
+ SCOPED_TRACE(Case.Code);
+
+ Annotations T(Case.Code);
+ TestTU TU = TestTU::withCode(T.code());
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get());
+ ASSERT_TRUE(H);
+ HoverInfo Expected;
+ Expected.SymRange = T.range();
+ Case.ExpectedBuilder(Expected);
+
+ SCOPED_TRACE(H->present().asPlainText());
+ EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
+ EXPECT_EQ(H->LocalScope, Expected.LocalScope);
+ EXPECT_EQ(H->Name, Expected.Name);
+ EXPECT_EQ(H->Kind, Expected.Kind);
+ EXPECT_EQ(H->Documentation, Expected.Documentation);
+ EXPECT_EQ(H->Definition, Expected.Definition);
+ EXPECT_EQ(H->Type, Expected.Type);
+ EXPECT_EQ(H->ReturnType, Expected.ReturnType);
+ EXPECT_EQ(H->Parameters, Expected.Parameters);
+ EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
+ EXPECT_EQ(H->SymRange, Expected.SymRange);
+ EXPECT_EQ(H->Value, Expected.Value);
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
index 2d86c91c7ec08..c52d0996893a3 100644
--- a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
+++ b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
@@ -210,6 +210,36 @@ TEST(Paragraph, NewLines) {
EXPECT_EQ(P.asPlainText(), "foo bar foo bar");
}
+TEST(Paragraph, BoldText) {
+ Paragraph P;
+ P.appendBoldText("");
+ EXPECT_EQ(P.asMarkdown(), "");
+ EXPECT_EQ(P.asPlainText(), "");
+
+ P.appendBoldText(" \n foo\nbar\n ");
+ EXPECT_EQ(P.asMarkdown(), "**foo bar**");
+ EXPECT_EQ(P.asPlainText(), "**foo bar**");
+
+ P.appendSpace().appendBoldText("foobar");
+ EXPECT_EQ(P.asMarkdown(), "**foo bar** **foobar**");
+ EXPECT_EQ(P.asPlainText(), "**foo bar** **foobar**");
+}
+
+TEST(Paragraph, EmphasizedText) {
+ Paragraph P;
+ P.appendEmphasizedText("");
+ EXPECT_EQ(P.asMarkdown(), "");
+ EXPECT_EQ(P.asPlainText(), "");
+
+ P.appendEmphasizedText(" \n foo\nbar\n ");
+ EXPECT_EQ(P.asMarkdown(), "*foo bar*");
+ EXPECT_EQ(P.asPlainText(), "*foo bar*");
+
+ P.appendSpace().appendEmphasizedText("foobar");
+ EXPECT_EQ(P.asMarkdown(), "*foo bar* *foobar*");
+ EXPECT_EQ(P.asPlainText(), "*foo bar* *foobar*");
+}
+
TEST(Document, Separators) {
Document D;
D.addParagraph().appendText("foo");
diff --git a/clang/include/clang/AST/Comment.h b/clang/include/clang/AST/Comment.h
index dd9906727293f..42686ff24076a 100644
--- a/clang/include/clang/AST/Comment.h
+++ b/clang/include/clang/AST/Comment.h
@@ -19,6 +19,7 @@
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Compiler.h"
namespace clang {
class Decl;
@@ -119,6 +120,11 @@ class Comment {
LLVM_PREFERRED_TYPE(CommandTraits::KnownCommandIDs)
unsigned CommandID : CommandInfo::NumCommandIDBits;
+
+ /// Describes the syntax that was used in a documentation command.
+ /// Contains values from CommandMarkerKind enum.
+ LLVM_PREFERRED_TYPE(CommandMarkerKind)
+ unsigned CommandMarker : 1;
};
enum { NumInlineCommandCommentBits = NumInlineContentCommentBits + 3 +
CommandInfo::NumCommandIDBits };
@@ -347,6 +353,16 @@ class InlineCommandComment : public InlineContentComment {
InlineCommandCommentBits.RenderKind = llvm::to_underlying(RK);
InlineCommandCommentBits.CommandID = CommandID;
}
+ InlineCommandComment(SourceLocation LocBegin, SourceLocation LocEnd,
+ unsigned CommandID, InlineCommandRenderKind RK,
+ CommandMarkerKind CommandMarker, ArrayRef<Argument> Args)
+ : InlineContentComment(CommentKind::InlineCommandComment, LocBegin,
+ LocEnd),
+ Args(Args) {
+ InlineCommandCommentBits.RenderKind = llvm::to_underlying(RK);
+ InlineCommandCommentBits.CommandID = CommandID;
+ InlineCommandCommentBits.CommandMarker = llvm::to_underlying(CommandMarker);
+ }
static bool classof(const Comment *C) {
return C->getCommentKind() == CommentKind::InlineCommandComment;
@@ -384,6 +400,11 @@ class InlineCommandComment : public InlineContentComment {
SourceRange getArgRange(unsigned Idx) const {
return Args[Idx].Range;
}
+
+ CommandMarkerKind getCommandMarker() const LLVM_READONLY {
+ return static_cast<CommandMarkerKind>(
+ InlineCommandCommentBits.CommandMarker);
+ }
};
/// Abstract class for opening and closing HTML tags. HTML tags are always
diff --git a/clang/include/clang/AST/CommentSema.h b/clang/include/clang/AST/CommentSema.h
index 916d7945329c5..3169e2b0d86b9 100644
--- a/clang/include/clang/AST/CommentSema.h
+++ b/clang/include/clang/AST/CommentSema.h
@@ -131,6 +131,7 @@ class Sema {
InlineCommandComment *actOnInlineCommand(SourceLocation CommandLocBegin,
SourceLocation CommandLocEnd,
unsigned CommandID,
+ CommandMarkerKind CommandMarker,
ArrayRef<Comment::Argument> Args);
InlineContentComment *actOnUnknownCommand(SourceLocation LocBegin,
diff --git a/clang/lib/AST/CommentParser.cpp b/clang/lib/AST/CommentParser.cpp
index 12ed8e3f1b79a..35c4a9a825d77 100644
--- a/clang/lib/AST/CommentParser.cpp
+++ b/clang/lib/AST/CommentParser.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "clang/AST/CommentParser.h"
+#include "clang/AST/Comment.h"
#include "clang/AST/CommentCommandTraits.h"
#include "clang/AST/CommentSema.h"
#include "clang/Basic/CharInfo.h"
@@ -569,6 +570,8 @@ BlockCommandComment *Parser::parseBlockCommand() {
InlineCommandComment *Parser::parseInlineCommand() {
assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
+ CommandMarkerKind CMK =
+ Tok.is(tok::backslash_command) ? CMK_Backslash : CMK_At;
const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
const Token CommandTok = Tok;
@@ -580,7 +583,7 @@ InlineCommandComment *Parser::parseInlineCommand() {
InlineCommandComment *IC = S.actOnInlineCommand(
CommandTok.getLocation(), CommandTok.getEndLocation(),
- CommandTok.getCommandID(), Args);
+ CommandTok.getCommandID(), CMK, Args);
if (Args.size() < Info->NumArgs) {
Diag(CommandTok.getEndLocation().getLocWithOffset(1),
diff --git a/clang/lib/AST/CommentSema.cpp b/clang/lib/AST/CommentSema.cpp
index bd2206bb8a3bc..2fdf31f65a1af 100644
--- a/clang/lib/AST/CommentSema.cpp
+++ b/clang/lib/AST/CommentSema.cpp
@@ -360,12 +360,13 @@ void Sema::actOnTParamCommandFinish(TParamCommandComment *Command,
InlineCommandComment *
Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
SourceLocation CommandLocEnd, unsigned CommandID,
+ CommandMarkerKind CommandMarker,
ArrayRef<Comment::Argument> Args) {
StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
- return new (Allocator)
- InlineCommandComment(CommandLocBegin, CommandLocEnd, CommandID,
- getInlineCommandRenderKind(CommandName), Args);
+ return new (Allocator) InlineCommandComment(
+ CommandLocBegin, CommandLocEnd, CommandID,
+ getInlineCommandRenderKind(CommandName), CommandMarker, Args);
}
InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
index b609d4a7462fb..f8c4838ab7ee3 100644
--- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
@@ -122,6 +122,7 @@ static_library("clangd") {
"SemanticHighlighting.cpp",
"SemanticSelection.cpp",
"SourceCode.cpp",
+ "SymbolDocumentation.cpp",
"SystemIncludeExtractor.cpp",
"TUScheduler.cpp",
"TidyProvider.cpp",
More information about the cfe-commits
mailing list