Skip to content

Commit 1af93ca

Browse files
committed
[clangd] Add doxygen parsing for hover information
1 parent a12744f commit 1af93ca

File tree

17 files changed

+1180
-166
lines changed

17 files changed

+1180
-166
lines changed

clang-tools-extra/clangd/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC
108108
SemanticHighlighting.cpp
109109
SemanticSelection.cpp
110110
SourceCode.cpp
111+
SymbolDocumentation.cpp
111112
SystemIncludeExtractor.cpp
112113
TidyProvider.cpp
113114
TUScheduler.cpp

clang-tools-extra/clangd/CodeCompletionStrings.cpp

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
#include "CodeCompletionStrings.h"
1010
#include "clang-c/Index.h"
1111
#include "clang/AST/ASTContext.h"
12+
#include "clang/AST/Comment.h"
13+
#include "clang/AST/CommentCommandTraits.h"
14+
#include "clang/AST/CommentLexer.h"
15+
#include "clang/AST/CommentParser.h"
16+
#include "clang/AST/CommentSema.h"
17+
#include "clang/AST/Decl.h"
1218
#include "clang/AST/RawCommentList.h"
1319
#include "clang/Basic/SourceManager.h"
1420
#include "clang/Sema/CodeCompleteConsumer.h"
@@ -100,14 +106,25 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
100106
// the comments for namespaces.
101107
return "";
102108
}
103-
const RawComment *RC = getCompletionComment(Ctx, &Decl);
104-
if (!RC)
105-
return "";
106-
// Sanity check that the comment does not come from the PCH. We choose to not
107-
// write them into PCH, because they are racy and slow to load.
108-
assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
109-
std::string Doc =
110-
RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
109+
110+
std::string Doc;
111+
112+
if (isa<ParmVarDecl>(Decl)) {
113+
// Parameters are documented in the function comment.
114+
if (const auto *FD = dyn_cast<FunctionDecl>(Decl.getDeclContext()))
115+
Doc = getParamDocString(Ctx.getCommentForDecl(FD, nullptr),
116+
Decl.getName(), Ctx.getCommentCommandTraits());
117+
} else {
118+
119+
const RawComment *RC = getCompletionComment(Ctx, &Decl);
120+
if (!RC)
121+
return "";
122+
// Sanity check that the comment does not come from the PCH. We choose to
123+
// not write them into PCH, because they are racy and slow to load.
124+
assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
125+
Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
126+
}
127+
111128
if (!looksLikeDocComment(Doc))
112129
return "";
113130
// Clang requires source to be UTF-8, but doesn't enforce this in comments.
@@ -316,5 +333,46 @@ std::string getReturnType(const CodeCompletionString &CCS) {
316333
return "";
317334
}
318335

336+
void docCommentToMarkup(
337+
markup::Document &Doc, llvm::StringRef Comment,
338+
llvm::BumpPtrAllocator &Allocator, comments::CommandTraits &Traits,
339+
std::optional<SymbolPrintedType> SymbolType,
340+
std::optional<SymbolPrintedType> SymbolReturnType,
341+
const std::optional<std::vector<SymbolParam>> &SymbolParameters) {
342+
343+
// The comment lexer expects doxygen markers, so add them back.
344+
// We need to use the /// style doxygen markers because the comment could
345+
// contain the closing the closing tag "*/" of a C Style "/** */" comment
346+
// which would break the parsing if we would just enclose the comment text
347+
// with "/** */".
348+
std::string CommentWithMarkers = "///";
349+
for (char C : Comment) {
350+
if (C == '\n') {
351+
CommentWithMarkers += "\n///";
352+
} else {
353+
CommentWithMarkers += C;
354+
}
355+
}
356+
SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers);
357+
358+
SourceManager &SourceMgr = SourceMgrForFile.get();
359+
// The doxygen Sema requires a Diagostics consumer, since it reports warnings
360+
// e.g. when parameters are not documented correctly.
361+
// These warnings are not relevant for us, so we can ignore them.
362+
SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer);
363+
364+
comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits,
365+
/*PP=*/nullptr);
366+
comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits,
367+
SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()),
368+
CommentWithMarkers.data(),
369+
CommentWithMarkers.data() + CommentWithMarkers.size());
370+
comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(),
371+
Traits);
372+
373+
fullCommentToMarkupDocument(Doc, P.parseFullComment(), Traits, SymbolType,
374+
SymbolReturnType, SymbolParameters);
375+
}
376+
319377
} // namespace clangd
320378
} // namespace clang

clang-tools-extra/clangd/CodeCompletionStrings.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@
1414
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
1515
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H
1616

17+
#include "SymbolDocumentation.h"
1718
#include "clang/Sema/CodeCompleteConsumer.h"
1819

1920
namespace clang {
2021
class ASTContext;
2122

23+
namespace comments {
24+
class CommandTraits;
25+
class FullComment;
26+
} // namespace comments
27+
2228
namespace clangd {
2329

2430
/// Gets a minimally formatted documentation comment of \p Result, with comment
@@ -67,6 +73,23 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
6773
/// is usually the return type of a function.
6874
std::string getReturnType(const CodeCompletionString &CCS);
6975

76+
/// \brief Parse the \p Comment as doxygen comment and save the result in the
77+
/// given markup Document \p Doc.
78+
///
79+
/// It is assumed that comment markers have already been stripped (e.g. via
80+
/// getDocComment()).
81+
///
82+
/// This uses the Clang doxygen comment parser to parse the comment, and then
83+
/// converts the parsed comment to a markup::Document. The resulting document is
84+
/// a combination of the symbol information \p SymbolType, \p SymbolReturnType,
85+
/// and \p SymbolParameters and the parsed doxygen comment.
86+
void docCommentToMarkup(
87+
markup::Document &Doc, llvm::StringRef Comment,
88+
llvm::BumpPtrAllocator &Allocator, comments::CommandTraits &Traits,
89+
std::optional<SymbolPrintedType> SymbolType,
90+
std::optional<SymbolPrintedType> SymbolReturnType,
91+
const std::optional<std::vector<SymbolParam>> &SymbolParameters);
92+
7093
} // namespace clangd
7194
} // namespace clang
7295

clang-tools-extra/clangd/Hover.cpp

Lines changed: 51 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "ParsedAST.h"
1818
#include "Selection.h"
1919
#include "SourceCode.h"
20+
#include "SymbolDocumentation.h"
2021
#include "clang-include-cleaner/Analysis.h"
2122
#include "clang-include-cleaner/IncludeSpeller.h"
2223
#include "clang-include-cleaner/Types.h"
@@ -40,6 +41,7 @@
4041
#include "clang/AST/Type.h"
4142
#include "clang/Basic/CharInfo.h"
4243
#include "clang/Basic/LLVM.h"
44+
#include "clang/Basic/LangOptions.h"
4345
#include "clang/Basic/SourceLocation.h"
4446
#include "clang/Basic/SourceManager.h"
4547
#include "clang/Basic/Specifiers.h"
@@ -160,14 +162,14 @@ const char *getMarkdownLanguage(const ASTContext &Ctx) {
160162
return LangOpts.ObjC ? "objective-c" : "cpp";
161163
}
162164

163-
HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
164-
const PrintingPolicy &PP) {
165+
SymbolPrintedType printType(QualType QT, ASTContext &ASTCtx,
166+
const PrintingPolicy &PP) {
165167
// TypePrinter doesn't resolve decltypes, so resolve them here.
166168
// FIXME: This doesn't handle composite types that contain a decltype in them.
167169
// We should rather have a printing policy for that.
168170
while (!QT.isNull() && QT->isDecltypeType())
169171
QT = QT->castAs<DecltypeType>()->getUnderlyingType();
170-
HoverInfo::PrintedType Result;
172+
SymbolPrintedType Result;
171173
llvm::raw_string_ostream OS(Result.Type);
172174
// Special case: if the outer type is a tag type without qualifiers, then
173175
// include the tag for extra clarity.
@@ -189,16 +191,16 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
189191
return Result;
190192
}
191193

192-
HoverInfo::PrintedType printType(const TemplateTypeParmDecl *TTP) {
193-
HoverInfo::PrintedType Result;
194+
SymbolPrintedType printType(const TemplateTypeParmDecl *TTP) {
195+
SymbolPrintedType Result;
194196
Result.Type = TTP->wasDeclaredWithTypename() ? "typename" : "class";
195197
if (TTP->isParameterPack())
196198
Result.Type += "...";
197199
return Result;
198200
}
199201

200-
HoverInfo::PrintedType printType(const NonTypeTemplateParmDecl *NTTP,
201-
const PrintingPolicy &PP) {
202+
SymbolPrintedType printType(const NonTypeTemplateParmDecl *NTTP,
203+
const PrintingPolicy &PP) {
202204
auto PrintedType = printType(NTTP->getType(), NTTP->getASTContext(), PP);
203205
if (NTTP->isParameterPack()) {
204206
PrintedType.Type += "...";
@@ -208,9 +210,9 @@ HoverInfo::PrintedType printType(const NonTypeTemplateParmDecl *NTTP,
208210
return PrintedType;
209211
}
210212

211-
HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
212-
const PrintingPolicy &PP) {
213-
HoverInfo::PrintedType Result;
213+
SymbolPrintedType printType(const TemplateTemplateParmDecl *TTP,
214+
const PrintingPolicy &PP) {
215+
SymbolPrintedType Result;
214216
llvm::raw_string_ostream OS(Result.Type);
215217
OS << "template <";
216218
llvm::StringRef Sep = "";
@@ -230,14 +232,14 @@ HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
230232
return Result;
231233
}
232234

233-
std::vector<HoverInfo::Param>
235+
std::vector<SymbolParam>
234236
fetchTemplateParameters(const TemplateParameterList *Params,
235237
const PrintingPolicy &PP) {
236238
assert(Params);
237-
std::vector<HoverInfo::Param> TempParameters;
239+
std::vector<SymbolParam> TempParameters;
238240

239241
for (const Decl *Param : *Params) {
240-
HoverInfo::Param P;
242+
SymbolParam P;
241243
if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) {
242244
P.Type = printType(TTP);
243245

@@ -351,41 +353,13 @@ void enhanceFromIndex(HoverInfo &Hover, const NamedDecl &ND,
351353
});
352354
}
353355

354-
// Default argument might exist but be unavailable, in the case of unparsed
355-
// arguments for example. This function returns the default argument if it is
356-
// available.
357-
const Expr *getDefaultArg(const ParmVarDecl *PVD) {
358-
// Default argument can be unparsed or uninstantiated. For the former we
359-
// can't do much, as token information is only stored in Sema and not
360-
// attached to the AST node. For the latter though, it is safe to proceed as
361-
// the expression is still valid.
362-
if (!PVD->hasDefaultArg() || PVD->hasUnparsedDefaultArg())
363-
return nullptr;
364-
return PVD->hasUninstantiatedDefaultArg() ? PVD->getUninstantiatedDefaultArg()
365-
: PVD->getDefaultArg();
366-
}
367-
368-
HoverInfo::Param toHoverInfoParam(const ParmVarDecl *PVD,
369-
const PrintingPolicy &PP) {
370-
HoverInfo::Param Out;
371-
Out.Type = printType(PVD->getType(), PVD->getASTContext(), PP);
372-
if (!PVD->getName().empty())
373-
Out.Name = PVD->getNameAsString();
374-
if (const Expr *DefArg = getDefaultArg(PVD)) {
375-
Out.Default.emplace();
376-
llvm::raw_string_ostream OS(*Out.Default);
377-
DefArg->printPretty(OS, nullptr, PP);
378-
}
379-
return Out;
380-
}
381-
382356
// Populates Type, ReturnType, and Parameters for function-like decls.
383357
void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D,
384358
const FunctionDecl *FD,
385359
const PrintingPolicy &PP) {
386360
HI.Parameters.emplace();
387361
for (const ParmVarDecl *PVD : FD->parameters())
388-
HI.Parameters->emplace_back(toHoverInfoParam(PVD, PP));
362+
HI.Parameters->emplace_back(createSymbolParam(PVD, PP));
389363

390364
// We don't want any type info, if name already contains it. This is true for
391365
// constructors/destructors and conversion operators.
@@ -626,6 +600,9 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP,
626600
HI.Name = printName(Ctx, *D);
627601
const auto *CommentD = getDeclForComment(D);
628602
HI.Documentation = getDeclComment(Ctx, *CommentD);
603+
// safe the language options to be able to create the comment::CommandTraits
604+
// to parse the documentation
605+
HI.CommentOpts = D->getASTContext().getLangOpts().CommentOpts;
629606
enhanceFromIndex(HI, *CommentD, Index);
630607
if (HI.Documentation.empty())
631608
HI.Documentation = synthesizeDocumentation(D);
@@ -812,7 +789,7 @@ HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok,
812789
return HI;
813790
}
814791

815-
std::string typeAsDefinition(const HoverInfo::PrintedType &PType) {
792+
std::string typeAsDefinition(const SymbolPrintedType &PType) {
816793
std::string Result;
817794
llvm::raw_string_ostream OS(Result);
818795
OS << PType.Type;
@@ -1095,7 +1072,7 @@ void maybeAddCalleeArgInfo(const SelectionTree::Node *N, HoverInfo &HI,
10951072

10961073
// Extract matching argument from function declaration.
10971074
if (const ParmVarDecl *PVD = Parameters[I]) {
1098-
HI.CalleeArgInfo.emplace(toHoverInfoParam(PVD, PP));
1075+
HI.CalleeArgInfo.emplace(createSymbolParam(PVD, PP));
10991076
if (N == &OuterNode)
11001077
PassType.PassBy = getPassMode(PVD->getType());
11011078
}
@@ -1455,30 +1432,38 @@ markup::Document HoverInfo::present() const {
14551432

14561433
// Put a linebreak after header to increase readability.
14571434
Output.addRuler();
1458-
// Print Types on their own lines to reduce chances of getting line-wrapped by
1459-
// editor, as they might be long.
1460-
if (ReturnType) {
1461-
// For functions we display signature in a list form, e.g.:
1462-
// → `x`
1463-
// Parameters:
1464-
// - `bool param1`
1465-
// - `int param2 = 5`
1466-
Output.addParagraph().appendText("").appendCode(
1467-
llvm::to_string(*ReturnType));
1468-
}
14691435

1470-
if (Parameters && !Parameters->empty()) {
1471-
Output.addParagraph().appendText("Parameters: ");
1472-
markup::BulletList &L = Output.addBulletList();
1473-
for (const auto &Param : *Parameters)
1474-
L.addItem().addParagraph().appendCode(llvm::to_string(Param));
1475-
}
1436+
if (!Documentation.empty()) {
1437+
llvm::BumpPtrAllocator Allocator;
1438+
comments::CommandTraits Traits(Allocator, CommentOpts);
1439+
docCommentToMarkup(Output, Documentation, Allocator, Traits, Type,
1440+
ReturnType, Parameters);
1441+
} else {
1442+
// Print Types on their own lines to reduce chances of getting line-wrapped
1443+
// by editor, as they might be long.
1444+
if (ReturnType) {
1445+
// For functions we display signature in a list form, e.g.:
1446+
// → `x`
1447+
// Parameters:
1448+
// - `bool param1`
1449+
// - `int param2 = 5`
1450+
Output.addParagraph().appendText("").appendCode(
1451+
llvm::to_string(*ReturnType));
1452+
}
14761453

1477-
// Don't print Type after Parameters or ReturnType as this will just duplicate
1478-
// the information
1479-
if (Type && !ReturnType && !Parameters)
1480-
Output.addParagraph().appendText("Type: ").appendCode(
1481-
llvm::to_string(*Type));
1454+
if (Parameters && !Parameters->empty()) {
1455+
Output.addParagraph().appendText("Parameters: ");
1456+
markup::BulletList &L = Output.addBulletList();
1457+
for (const auto &Param : *Parameters)
1458+
L.addItem().addParagraph().appendCode(llvm::to_string(Param));
1459+
}
1460+
1461+
// Don't print Type after Parameters or ReturnType as this will just
1462+
// duplicate the information
1463+
if (Type && !ReturnType && !Parameters)
1464+
Output.addParagraph().appendText("Type: ").appendCode(
1465+
llvm::to_string(*Type));
1466+
}
14821467

14831468
if (Value) {
14841469
markup::Paragraph &P = Output.addParagraph();
@@ -1518,9 +1503,6 @@ markup::Document HoverInfo::present() const {
15181503
Output.addParagraph().appendText(OS.str());
15191504
}
15201505

1521-
if (!Documentation.empty())
1522-
parseDocumentation(Documentation, Output);
1523-
15241506
if (!Definition.empty()) {
15251507
Output.addRuler();
15261508
std::string Buffer;
@@ -1646,26 +1628,5 @@ void parseDocumentation(llvm::StringRef Input, markup::Document &Output) {
16461628
FlushParagraph();
16471629
}
16481630

1649-
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
1650-
const HoverInfo::PrintedType &T) {
1651-
OS << T.Type;
1652-
if (T.AKA)
1653-
OS << " (aka " << *T.AKA << ")";
1654-
return OS;
1655-
}
1656-
1657-
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
1658-
const HoverInfo::Param &P) {
1659-
if (P.Type)
1660-
OS << P.Type->Type;
1661-
if (P.Name)
1662-
OS << " " << *P.Name;
1663-
if (P.Default)
1664-
OS << " = " << *P.Default;
1665-
if (P.Type && P.Type->AKA)
1666-
OS << " (aka " << *P.Type->AKA << ")";
1667-
return OS;
1668-
}
1669-
16701631
} // namespace clangd
16711632
} // namespace clang

0 commit comments

Comments
 (0)