https://github.com/tcottin updated https://github.com/llvm/llvm-project/pull/152918
>From bdb6f26c036436c9c964333f7bd43b10181a860e Mon Sep 17 00:00:00 2001 From: Tim Cottin <timcot...@gmx.de> Date: Sun, 10 Aug 2025 13:51:24 +0000 Subject: [PATCH 1/2] [clangd] rearrange doxygen hover documentation --- .../clangd/CodeCompletionStrings.cpp | 8 +- clang-tools-extra/clangd/Hover.cpp | 100 ++++- clang-tools-extra/clangd/Hover.h | 3 + .../clangd/SymbolDocumentation.cpp | 103 ++++- .../clangd/SymbolDocumentation.h | 76 +++- .../clangd/unittests/HoverTests.cpp | 377 ++++++++++++++++-- .../unittests/SymbolDocumentationTests.cpp | 134 +++++-- 7 files changed, 683 insertions(+), 118 deletions(-) diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index d6579640cb0fb..02c3ea8abd8af 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -112,7 +112,7 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { std::string Doc; if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen && - isa<ParmVarDecl>(Decl)) { + (isa<ParmVarDecl>(Decl) || isa<TemplateTypeParmDecl>(Decl))) { // Parameters are documented in their declaration context (function or // template function). const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext()); @@ -135,7 +135,11 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { std::string RawDoc; llvm::raw_string_ostream OS(RawDoc); - V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS); + if (isa<ParmVarDecl>(Decl)) + V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS); + else + V.templateTypeParmDocToString( + dyn_cast<TemplateTypeParmDecl>(&Decl)->getName(), OS); Doc = StringRef(RawDoc).trim().str(); } else { diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 0afa90285db52..45e47233c9e1f 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -1276,6 +1276,7 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos, HI.Definition = URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str(); HI.DefinitionLanguage = ""; + HI.IsIncludeDirective = true; maybeAddUsedSymbols(AST, HI, Inc); return HI; } @@ -1504,7 +1505,26 @@ markup::Document HoverInfo::presentDoxygen() const { Header.appendText(index::getSymbolKindString(Kind)).appendSpace(); assert(!Name.empty() && "hover triggered on a nameless symbol"); - Header.appendCode(Name); + if (IsIncludeDirective) { + Header.appendCode(Name); + + if (!Definition.empty()) + Output.addParagraph().appendCode(Definition); + + if (!UsedSymbolNames.empty()) { + Output.addRuler(); + usedSymbolNamesToMarkup(Output); + } + + return Output; + } + + if (!Definition.empty()) { + Output.addRuler(); + definitionScopeToMarkup(Output); + } else { + Header.appendCode(Name); + } if (!Provider.empty()) { providerToMarkupParagraph(Output); @@ -1512,33 +1532,76 @@ markup::Document HoverInfo::presentDoxygen() 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)); - } SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts); + if (SymbolDoc.hasBriefCommand()) { + SymbolDoc.briefToMarkup(Output.addParagraph()); + Output.addRuler(); + } + + // For functions we display signature in a list form, e.g.: + // Template Parameters: + // - `typename T` - description + // Parameters: + // - `bool param1` - description + // - `int param2 = 5` - description + // Returns + // `type` - description + if (TemplateParameters && !TemplateParameters->empty()) { + Output.addParagraph().appendBoldText("Template Parameters:"); + markup::BulletList &L = Output.addBulletList(); + for (const auto &Param : *TemplateParameters) { + markup::Paragraph &P = L.addItem().addParagraph(); + P.appendCode(llvm::to_string(Param)); + if (SymbolDoc.isTemplateTypeParmDocumented(llvm::to_string(Param.Name))) { + P.appendText(" - "); + SymbolDoc.templateTypeParmDocToMarkup(llvm::to_string(Param.Name), P); + } + } + Output.addRuler(); + } + if (Parameters && !Parameters->empty()) { - Output.addParagraph().appendText("Parameters:"); + Output.addParagraph().appendBoldText("Parameters:"); markup::BulletList &L = Output.addBulletList(); for (const auto &Param : *Parameters) { markup::Paragraph &P = L.addItem().addParagraph(); P.appendCode(llvm::to_string(Param)); if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) { - P.appendText(" -"); + P.appendText(" - "); SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P); } } + Output.addRuler(); + } + + // Print Types on their own lines to reduce chances of getting line-wrapped by + // editor, as they might be long. + if (ReturnType && + ((ReturnType->Type != "void" && !ReturnType->AKA.has_value()) || + (ReturnType->AKA.has_value() && ReturnType->AKA != "void"))) { + Output.addParagraph().appendBoldText("Returns:"); + markup::Paragraph &P = Output.addParagraph(); + P.appendCode(llvm::to_string(*ReturnType)); + + if (SymbolDoc.hasReturnCommand()) { + P.appendText(" - "); + SymbolDoc.returnToMarkup(P); + } + Output.addRuler(); } + + // add specially handled doxygen commands. + SymbolDoc.warningsToMarkup(Output); + SymbolDoc.notesToMarkup(Output); + + // add any other documentation. + SymbolDoc.docToMarkup(Output); + + Output.addRuler(); + // Don't print Type after Parameters or ReturnType as this will just duplicate // the information if (Type && !ReturnType && !Parameters) @@ -1559,13 +1622,6 @@ markup::Document HoverInfo::presentDoxygen() const { calleeArgInfoToMarkupParagraph(Output.addParagraph()); } - SymbolDoc.docToMarkup(Output); - - if (!Definition.empty()) { - Output.addRuler(); - definitionScopeToMarkup(Output); - } - if (!UsedSymbolNames.empty()) { Output.addRuler(); usedSymbolNamesToMarkup(Output); @@ -1613,7 +1669,7 @@ markup::Document HoverInfo::presentDefault() const { } if (Parameters && !Parameters->empty()) { - Output.addParagraph().appendText("Parameters: "); + Output.addParagraph().appendText("Parameters:"); markup::BulletList &L = Output.addBulletList(); for (const auto &Param : *Parameters) L.addItem().addParagraph().appendCode(llvm::to_string(Param)); diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h index 614180a7b9846..2eb3dd99ab3fc 100644 --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -120,6 +120,9 @@ struct HoverInfo { // alphabetical order. std::vector<std::string> UsedSymbolNames; + /// True if this symbol is a include directive. + bool IsIncludeDirective = false; + /// Produce a user-readable information based on the specified markup kind. std::string present(MarkupKind Kind) const; diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp index dea637b9100da..9152355cd0f79 100644 --- a/clang-tools-extra/clangd/SymbolDocumentation.cpp +++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp @@ -33,8 +33,8 @@ void commandToMarkup(markup::Paragraph &Out, StringRef Command, comments::CommandMarkerKind CommandMarker, StringRef Args) { Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str()); + Out.appendSpace(); if (!Args.empty()) { - Out.appendSpace(); Out.appendEmphasizedText(Args.str()); } } @@ -132,7 +132,8 @@ class ParagraphToMarkupDocument const comments::CommandTraits &Traits; /// If true, the next leading space after a new line is trimmed. - bool LastChunkEndsWithNewline = false; + /// Initially set it to true, to always trim the first text line. + bool LastChunkEndsWithNewline = true; }; class ParagraphToString @@ -263,8 +264,76 @@ class BlockCommentToMarkupDocument StringRef CommentEscapeMarker; }; -void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName, - markup::Paragraph &Out) { +void SymbolDocCommentVisitor::visitBlockCommandComment( + const comments::BlockCommandComment *B) { + switch (B->getCommandID()) { + case comments::CommandTraits::KCI_brief: { + if (!BriefParagraph) { + BriefParagraph = B->getParagraph(); + return; + } + break; + } + case comments::CommandTraits::KCI_return: + case comments::CommandTraits::KCI_returns: + if (!ReturnParagraph) { + ReturnParagraph = B->getParagraph(); + return; + } + break; + case comments::CommandTraits::KCI_retval: + RetvalParagraphs.push_back(B->getParagraph()); + return; + case comments::CommandTraits::KCI_warning: + WarningParagraphs.push_back(B->getParagraph()); + return; + case comments::CommandTraits::KCI_note: + NoteParagraphs.push_back(B->getParagraph()); + return; + default: + break; + } + + // For all other commands, we store them in the UnhandledCommands map. + // This allows us to keep the order of the comments. + UnhandledCommands[CommentPartIndex] = B; + CommentPartIndex++; +} + +void SymbolDocCommentVisitor::paragraphsToMarkup( + markup::Document &Out, + const llvm::SmallVector<const comments::ParagraphComment *> &Paragraphs) + const { + if (Paragraphs.empty()) + return; + + for (const auto *P : Paragraphs) { + ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P); + } +} + +void SymbolDocCommentVisitor::briefToMarkup(markup::Paragraph &Out) const { + if (!BriefParagraph) + return; + ParagraphToMarkupDocument(Out, Traits).visit(BriefParagraph); +} + +void SymbolDocCommentVisitor::returnToMarkup(markup::Paragraph &Out) const { + if (!ReturnParagraph) + return; + ParagraphToMarkupDocument(Out, Traits).visit(ReturnParagraph); +} + +void SymbolDocCommentVisitor::notesToMarkup(markup::Document &Out) const { + paragraphsToMarkup(Out, NoteParagraphs); +} + +void SymbolDocCommentVisitor::warningsToMarkup(markup::Document &Out) const { + paragraphsToMarkup(Out, WarningParagraphs); +} + +void SymbolDocCommentVisitor::parameterDocToMarkup( + StringRef ParamName, markup::Paragraph &Out) const { if (ParamName.empty()) return; @@ -274,7 +343,7 @@ void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName, } void SymbolDocCommentVisitor::parameterDocToString( - StringRef ParamName, llvm::raw_string_ostream &Out) { + StringRef ParamName, llvm::raw_string_ostream &Out) const { if (ParamName.empty()) return; @@ -283,9 +352,9 @@ void SymbolDocCommentVisitor::parameterDocToString( } } -void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) { +void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) const { for (unsigned I = 0; I < CommentPartIndex; ++I) { - if (const auto *BC = BlockCommands.lookup(I)) { + if (const auto *BC = UnhandledCommands.lookup(I)) { BlockCommentToMarkupDocument(Out, Traits).visit(BC); } else if (const auto *P = FreeParagraphs.lookup(I)) { ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P); @@ -293,5 +362,25 @@ void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) { } } +void SymbolDocCommentVisitor::templateTypeParmDocToMarkup( + StringRef TemplateParamName, markup::Paragraph &Out) const { + if (TemplateParamName.empty()) + return; + + if (const auto *TP = TemplateParameters.lookup(TemplateParamName)) { + ParagraphToMarkupDocument(Out, Traits).visit(TP->getParagraph()); + } +} + +void SymbolDocCommentVisitor::templateTypeParmDocToString( + StringRef TemplateParamName, llvm::raw_string_ostream &Out) const { + if (TemplateParamName.empty()) + return; + + if (const auto *P = TemplateParameters.lookup(TemplateParamName)) { + ParagraphToString(Out, Traits).visit(P->getParagraph()); + } +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h index b5120ba04e8f1..9d173745965e2 100644 --- a/clang-tools-extra/clangd/SymbolDocumentation.h +++ b/clang-tools-extra/clangd/SymbolDocumentation.h @@ -106,16 +106,43 @@ class SymbolDocCommentVisitor return Parameters.contains(ParamName); } - void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out); + bool isTemplateTypeParmDocumented(StringRef ParamName) const { + return TemplateParameters.contains(ParamName); + } - void parameterDocToString(StringRef ParamName, llvm::raw_string_ostream &Out); + bool hasBriefCommand() const { return BriefParagraph; } - void docToMarkup(markup::Document &Out); + bool hasReturnCommand() const { return ReturnParagraph; } - void visitBlockCommandComment(const comments::BlockCommandComment *B) { - BlockCommands[CommentPartIndex] = B; - CommentPartIndex++; - } + bool hasRetvalCommands() const { return !RetvalParagraphs.empty(); } + + bool hasNoteCommands() const { return !NoteParagraphs.empty(); } + + bool hasWarningCommands() const { return !WarningParagraphs.empty(); } + + /// Converts all unhandled comment commands to a markup document. + void docToMarkup(markup::Document &Out) const; + /// Converts the "brief" command(s) to a markup document. + void briefToMarkup(markup::Paragraph &Out) const; + /// Converts the "return" command(s) to a markup document. + void returnToMarkup(markup::Paragraph &Out) const; + /// Converts the "note" command(s) to a markup document. + void notesToMarkup(markup::Document &Out) const; + /// Converts the "warning" command(s) to a markup document. + void warningsToMarkup(markup::Document &Out) const; + + void visitBlockCommandComment(const comments::BlockCommandComment *B); + + void templateTypeParmDocToMarkup(StringRef TemplateParamName, + markup::Paragraph &Out) const; + + void templateTypeParmDocToString(StringRef TemplateParamName, + llvm::raw_string_ostream &Out) const; + + void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out) const; + + void parameterDocToString(StringRef ParamName, + llvm::raw_string_ostream &Out) const; void visitParagraphComment(const comments::ParagraphComment *P) { FreeParagraphs[CommentPartIndex] = P; @@ -126,6 +153,10 @@ class SymbolDocCommentVisitor Parameters[P->getParamNameAsWritten()] = P; } + void visitTParamCommandComment(const comments::TParamCommandComment *TP) { + TemplateParameters[TP->getParamNameAsWritten()] = std::move(TP); + } + private: comments::CommandTraits Traits; llvm::BumpPtrAllocator Allocator; @@ -136,17 +167,42 @@ class SymbolDocCommentVisitor /// This index allows us to keep the order of the other comment parts. unsigned CommentPartIndex = 0; + /// Paragraph of the "brief" command. + const comments::ParagraphComment *BriefParagraph = nullptr; + + /// Paragraph of the "return" command. + const comments::ParagraphComment *ReturnParagraph = nullptr; + + /// Paragraph(s) of the "note" command(s) + llvm::SmallVector<const comments::ParagraphComment *> RetvalParagraphs; + + /// 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; + + /// All the paragraphs we don't have any special handling for, + /// e.g. "details". + llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *> + UnhandledCommands; + /// Parsed paragaph(s) of the "param" comamnd(s) llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *> Parameters; - /// All the block commands. - llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *> - BlockCommands; + /// Parsed paragaph(s) of the "tparam" comamnd(s) + llvm::SmallDenseMap<StringRef, const comments::TParamCommandComment *> + TemplateParameters; /// All "free" text paragraphs. llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *> FreeParagraphs; + + void + paragraphsToMarkup(markup::Document &Out, + const llvm::SmallVector<const comments::ParagraphComment *> + &Paragraphs) const; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 3331164ab0024..f7e88c66973f3 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -3409,7 +3409,8 @@ TEST(Hover, DocsFromMostSpecial) { TEST(Hover, Present) { struct { const std::function<void(HoverInfo &)> Builder; - llvm::StringRef ExpectedRender; + llvm::StringRef ExpectedMarkdownRender; + llvm::StringRef ExpectedDoxygenRender; } Cases[] = { { [](HoverInfo &HI) { @@ -3417,6 +3418,7 @@ TEST(Hover, Present) { HI.Name = "X"; }, R"(X)", + R"(### `X`)", }, { [](HoverInfo &HI) { @@ -3424,6 +3426,7 @@ TEST(Hover, Present) { HI.Name = "foo"; }, R"(namespace-alias foo)", + R"(### namespace-alias `foo`)", }, { [](HoverInfo &HI) { @@ -3446,6 +3449,24 @@ Size: 10 bytes documentation template <typename T, typename C = bool> class Foo {})", + R"(### class + +--- +```cpp +template <typename T, typename C = bool> class Foo {} +``` + +--- +**Template Parameters:** + +- `typename T` +- `typename C = bool` + +--- +documentation + +--- +Size: 10 bytes)", }, { [](HoverInfo &HI) { @@ -3476,6 +3497,26 @@ template <typename T, typename C = bool> class Foo {})", "\n" "// In namespace ns\n" "ret_type foo(params) {}", + R"(### function + +--- +```cpp +// In namespace ns +ret_type foo(params) {} +``` + +--- +**Parameters:** + +- +- `type (aka can_type)` +- `type foo (aka can_type)` +- `type foo = default (aka can_type)` + +--- +**Returns:** + +`ret_type (aka can_ret_type)`)", }, { [](HoverInfo &HI) { @@ -3502,6 +3543,22 @@ Size: 4 bytes (+4 bytes padding), alignment 4 bytes // In test::Bar def)", + R"(### field + +--- +```cpp +// In test::Bar +def +``` + +--- +Type: `type (aka can_type)` + +Value = `value` + +Offset: 12 bytes + +Size: 4 bytes (+4 bytes padding), alignment 4 bytes)", }, { [](HoverInfo &HI) { @@ -3528,6 +3585,22 @@ Size: 25 bits (+4 bits padding), alignment 8 bytes // In test::Bar def)", + R"(### field + +--- +```cpp +// In test::Bar +def +``` + +--- +Type: `type (aka can_type)` + +Value = `value` + +Offset: 4 bytes and 3 bits + +Size: 25 bits (+4 bits padding), alignment 8 bytes)", }, { [](HoverInfo &HI) { @@ -3541,6 +3614,13 @@ def)", // In test::Bar public: def)", + R"(### field + +--- +```cpp +// In test::Bar +public: def +```)", }, { [](HoverInfo &HI) { @@ -3560,6 +3640,18 @@ public: def)", // In cls<int> protected: size_t method())", + R"(### instance-method + +--- +```cpp +// In cls<int> +protected: size_t method() +``` + +--- +**Returns:** + +`size_t (aka unsigned long)`)", }, { [](HoverInfo &HI) { @@ -3587,6 +3679,19 @@ protected: size_t method())", // In cls public: cls(int a, int b = 5))", + R"(### constructor + +--- +```cpp +// In cls +public: cls(int a, int b = 5) +``` + +--- +**Parameters:** + +- `int a` +- `int b = 5`)", }, { [](HoverInfo &HI) { @@ -3600,6 +3705,13 @@ public: cls(int a, int b = 5))", // In namespace ns1 private: union foo {})", + R"(### union + +--- +```cpp +// In namespace ns1 +private: union foo {} +```)", }, { [](HoverInfo &HI) { @@ -3625,6 +3737,20 @@ Passed as arg_a // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed as arg_a)", }, { [](HoverInfo &HI) { @@ -3636,6 +3762,10 @@ int foo = 3)", }, R"(variable foo +Passed by value)", + R"(### variable `foo` + +--- Passed by value)", }, { @@ -3662,6 +3792,20 @@ Passed by reference as arg_a // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed by reference as arg_a)", }, { [](HoverInfo &HI) { @@ -3687,6 +3831,20 @@ Passed as arg_a (converted to alias_int) // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed as arg_a (converted to alias_int))", }, { [](HoverInfo &HI) { @@ -3702,6 +3860,15 @@ int foo = 3)", // Expands to (1 + 1))", + R"(### macro + +--- +```cpp +#define PLUS_ONE(X) (X+1) + +// Expands to +(1 + 1) +```)", }, { [](HoverInfo &HI) { @@ -3727,45 +3894,86 @@ Passed by const reference as arg_a (converted to int) // In test::Bar int foo = 3)", + R"(### variable + +--- +```cpp +// In test::Bar +int foo = 3 +``` + +--- +Type: `int` + +Value = `3` + +Passed by const reference as arg_a (converted to int))", }, { [](HoverInfo &HI) { HI.Name = "stdio.h"; HI.Definition = "/usr/include/stdio.h"; + HI.IsIncludeDirective = true; }, R"(stdio.h /usr/include/stdio.h)", + R"(### `stdio.h` + +`/usr/include/stdio.h`)", }, - {[](HoverInfo &HI) { - HI.Name = "foo.h"; - HI.UsedSymbolNames = {"Foo", "Bar", "Bar"}; - }, - R"(foo.h + { + [](HoverInfo &HI) { + HI.Name = "foo.h"; + HI.UsedSymbolNames = {"Foo", "Bar", "Bar"}; + HI.IsIncludeDirective = true; + }, + R"(foo.h + +provides Foo, Bar, Bar)", + R"(### `foo.h` -provides Foo, Bar, Bar)"}, +--- +provides `Foo`, `Bar`, `Bar`)", + }, {[](HoverInfo &HI) { HI.Name = "foo.h"; HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"}; + HI.IsIncludeDirective = true; }, R"(foo.h -provides Foo, Bar, Baz, Foobar, Qux and 1 more)"}}; +provides Foo, Bar, Baz, Foobar, Qux and 1 more)", + R"(### `foo.h` + +--- +provides `Foo`, `Bar`, `Baz`, `Foobar`, `Qux` and 1 more)"}}; for (const auto &C : Cases) { HoverInfo HI; C.Builder(HI); Config Cfg; Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown; WithContextValue WithCfg(Config::Key, std::move(Cfg)); - EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedRender); + EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedMarkdownRender); + } + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender); } } TEST(Hover, PresentDocumentation) { struct { const std::function<void(HoverInfo &)> Builder; - llvm::StringRef ExpectedRender; + llvm::StringRef ExpectedMarkdownRender; + llvm::StringRef ExpectedDoxygenRender; } Cases[] = { {[](HoverInfo &HI) { HI.Kind = index::SymbolKind::Function; @@ -3777,14 +3985,26 @@ TEST(Hover, PresentDocumentation) { R"(### function `foo` --- -**@brief** brief doc +@brief brief doc longer doc --- ```cpp void foo() -```)"}, +```)", + R"(### function + +--- +```cpp +void foo() +``` + +--- +brief doc + +--- +longer doc)"}, {[](HoverInfo &HI) { HI.Kind = index::SymbolKind::Function; HI.Documentation = "@brief brief doc\n\n" @@ -3798,14 +4018,31 @@ void foo() --- → `int` -**@brief** brief doc +@brief brief doc longer doc --- ```cpp int foo() -```)"}, +```)", + R"(### function + +--- +```cpp +int foo() +``` + +--- +brief doc + +--- +**Returns:** + +`int` + +--- +longer doc)"}, {[](HoverInfo &HI) { HI.Kind = index::SymbolKind::Function; HI.Documentation = "@brief brief doc\n\n" @@ -3826,18 +4063,40 @@ int foo() Parameters: -- `int a` - this is a param +- `int a` -**@brief** brief doc +@brief brief doc longer doc +@param a this is a param +@return it returns something -**@return** it returns something +--- +```cpp +int foo(int a) +```)", + R"(### function --- ```cpp int foo(int a) -```)"}, +``` + +--- +brief doc + +--- +**Parameters:** + +- `int a` - this is a param + +--- +**Returns:** + +`int` - it returns something + +--- +longer doc)"}, {[](HoverInfo &HI) { HI.Kind = index::SymbolKind::Function; HI.Documentation = "@brief brief doc\n\n" @@ -3858,20 +4117,52 @@ int foo(int a) Parameters: -- `int a` - this is a param +- `int a` -**@brief** brief doc +@brief brief doc longer doc +@param a this is a param +@param b does not exist +@return it returns something -**@return** it returns something +--- +```cpp +int foo(int a) +```)", + R"(### function --- ```cpp int foo(int a) -```)"}, +``` + +--- +brief doc + +--- +**Parameters:** + +- `int a` - this is a param + +--- +**Returns:** + +`int` - it returns something + +--- +longer doc)"}, }; + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedMarkdownRender); + } for (const auto &C : Cases) { HoverInfo HI; C.Builder(HI); @@ -3879,7 +4170,7 @@ int foo(int a) Cfg.Hover.ShowAKA = true; Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; WithContextValue WithCfg(Config::Key, std::move(Cfg)); - EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedRender); + EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender); } } @@ -4057,6 +4348,21 @@ TEST(Hover, PresentRulers) { "```"; EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedMarkdown); + llvm::StringRef ExpectedDoxygenMarkdown = // + "### variable\n" + "\n" + "---\n" + "```cpp\n" + "def\n" + "```\n\n" + "---\n" + "Value = `val`"; + Config Cfg; + Cfg.Hover.ShowAKA = true; + Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedDoxygenMarkdown); + llvm::StringRef ExpectedPlaintext = R"pt(variable foo Value = val @@ -4479,8 +4785,7 @@ TEST(Hover, FunctionParameters) { HI.Definition = "int a"; HI.Documentation = ""; }, - "### param `a`\n\n---\nType: `int`\n\n---\n```cpp\n// In foo\nint " - "a\n```"}, + "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"}, {R"cpp(/// Function doc /// @param a this is doc for a void foo(int [[^a]]); @@ -4494,8 +4799,8 @@ TEST(Hover, FunctionParameters) { HI.Definition = "int a"; HI.Documentation = "this is doc for a"; }, - "### param `a`\n\n---\nType: `int`\n\nthis is doc for " - "a\n\n---\n```cpp\n// In foo\nint a\n```"}, + "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nthis is doc " + "for a\n\n---\nType: `int`"}, {R"cpp(/// Function doc /// @param b this is doc for b void foo(int [[^a]], int b); @@ -4509,8 +4814,7 @@ TEST(Hover, FunctionParameters) { HI.Definition = "int a"; HI.Documentation = ""; }, - "### param `a`\n\n---\nType: `int`\n\n---\n```cpp\n// In foo\nint " - "a\n```"}, + "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"}, {R"cpp(/// Function doc /// @param b this is doc for \p b void foo(int a, int [[^b]]); @@ -4524,8 +4828,8 @@ TEST(Hover, FunctionParameters) { HI.Definition = "int b"; HI.Documentation = "this is doc for \\p b"; }, - "### param `b`\n\n---\nType: `int`\n\nthis is doc for " - "`b`\n\n---\n```cpp\n// In foo\nint b\n```"}, + "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is doc " + "for `b`\n\n---\nType: `int`"}, {R"cpp(/// Function doc /// @param b this is doc for \p b template <typename T> @@ -4540,8 +4844,8 @@ TEST(Hover, FunctionParameters) { HI.Definition = "T b"; HI.Documentation = "this is doc for \\p b"; }, - "### param `b`\n\n---\nType: `T`\n\nthis is doc for " - "`b`\n\n---\n```cpp\n// In foo\nT b\n```"}, + "### param\n\n---\n```cpp\n// In foo\nT b\n```\n\n---\nthis is doc for " + "`b`\n\n---\nType: `T`"}, {R"cpp(/// Function doc /// @param b this is <b>doc</b> <html-tag attribute/> <another-html-tag attribute="value">for</another-html-tag> \p b void foo(int a, int [[^b]]); @@ -4557,10 +4861,9 @@ TEST(Hover, FunctionParameters) { "this is <b>doc</b> <html-tag attribute/> <another-html-tag " "attribute=\"value\">for</another-html-tag> \\p b"; }, - "### param `b`\n\n---\nType: `int`\n\nthis is \\<b>doc\\</b> " - "\\<html-tag attribute/> \\<another-html-tag " - "attribute=\"value\">for\\</another-html-tag> " - "`b`\n\n---\n```cpp\n// In foo\nint b\n```"}, + "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is " + "\\<b>doc\\</b> \\<html-tag attribute/> \\<another-html-tag " + "attribute=\"value\">for\\</another-html-tag> `b`\n\n---\nType: `int`"}, }; // Create a tiny index, so tests above can verify documentation is fetched. diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp index 69eb13b2142d2..31a6a149e33c4 100644 --- a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp @@ -15,7 +15,7 @@ namespace clang { namespace clangd { -TEST(SymbolDocumentation, Parse) { +TEST(SymbolDocumentation, UnhandledDocs) { CommentOptions CommentOpts; @@ -75,9 +75,9 @@ TEST(SymbolDocumentation, Parse) { }, { "\\brief this is a \\n\nbrief description", - "\\*\\*\\\\brief\\*\\* this is a \nbrief description", - "**\\brief** this is a \nbrief description", - "**\\brief** this is a\nbrief description", + "", + "", + "", }, { "\\throw exception foo", @@ -86,18 +86,26 @@ TEST(SymbolDocumentation, Parse) { "**\\throw** *exception* foo", }, { - "\\brief this is a brief description\n\n\\li item 1\n\\li item " - "2\n\\arg item 3", - "\\*\\*\\\\brief\\*\\* this is a brief description\n\n- item 1\n\n- " - "item " - "2\n\n- " - "item 3", - "**\\brief** this is a brief description\n\n- item 1\n\n- item " - "2\n\n- " - "item 3", - "**\\brief** this is a brief description\n\n- item 1\n\n- item " - "2\n\n- " - "item 3", + R"(\brief this is a brief description + +\li item 1 +\li item 2 +\arg item 3)", + R"(- item 1 + +- item 2 + +- item 3)", + R"(- item 1 + +- item 2 + +- item 3)", + R"(- item 1 + +- item 2 + +- item 3)", }, { "\\defgroup mygroup this is a group\nthis is not a group description", @@ -110,15 +118,36 @@ TEST(SymbolDocumentation, Parse) { "description", }, { - "\\verbatim\nthis is a\nverbatim block containing\nsome verbatim " - "text\n\\endverbatim", - "\\*\\*@verbatim\\*\\*\n\n```\nthis is a\nverbatim block " - "containing\nsome " - "verbatim text\n```\n\n\\*\\*@endverbatim\\*\\*", - "**@verbatim**\n\n```\nthis is a\nverbatim block containing\nsome " - "verbatim text\n```\n\n**@endverbatim**", - "**@verbatim**\n\nthis is a\nverbatim block containing\nsome " - "verbatim text\n\n**@endverbatim**", + R"(\verbatim +this is a +verbatim block containing +some verbatim text +\endverbatim)", + R"(\*\*@verbatim\*\* + +``` +this is a +verbatim block containing +some verbatim text +``` + +\*\*@endverbatim\*\*)", + R"(**@verbatim** + +``` +this is a +verbatim block containing +some verbatim text +``` + +**@endverbatim**)", + R"(**@verbatim** + +this is a +verbatim block containing +some verbatim text + +**@endverbatim**)", }, { "@param foo this is a parameter\n@param bar this is another " @@ -128,21 +157,46 @@ TEST(SymbolDocumentation, Parse) { "", }, { - "@brief brief docs\n\n@param foo this is a parameter\n\nMore " - "description\ndocumentation", - "\\*\\*@brief\\*\\* brief docs\n\nMore description\ndocumentation", - "**@brief** brief docs\n\nMore description\ndocumentation", - "**@brief** brief docs\n\nMore description documentation", - }, - { - "<b>this is a bold text</b>\nnormal text\n<i>this is an italic " - "text</i>\n<code>this is a code block</code>", - "\\<b>this is a bold text\\</b>\nnormal text\n\\<i>this is an italic " - "text\\</i>\n\\<code>this is a code block\\</code>", - "\\<b>this is a bold text\\</b>\nnormal text\n\\<i>this is an italic " - "text\\</i>\n\\<code>this is a code block\\</code>", - "<b>this is a bold text</b> normal text <i>this is an italic " - "text</i> <code>this is a code block</code>", + R"(@brief brief docs + +@param foo this is a parameter + +\brief another brief? + +\details these are details + +More description +documentation)", + R"(\*\*\\brief\*\* another brief? + +\*\*\\details\*\* these are details + +More description +documentation)", + R"(**\brief** another brief? + +**\details** these are details + +More description +documentation)", + R"(**\brief** another brief? + +**\details** these are details + +More description documentation)", + }, + { + R"(<b>this is a bold text</b> +normal text<i>this is an italic text</i> +<code>this is a code block</code>)", + R"(\<b>this is a bold text\</b> +normal text\<i>this is an italic text\</i> +\<code>this is a code block\</code>)", + R"(\<b>this is a bold text\</b> +normal text\<i>this is an italic text\</i> +\<code>this is a code block\</code>)", + "<b>this is a bold text</b> normal text<i>this is an italic text</i> " + "<code>this is a code block</code>", }, }; for (const auto &C : Cases) { >From 607bcc216bf38f8b9a862dc914e99d73e6be75a0 Mon Sep 17 00:00:00 2001 From: Tim Cottin <timcot...@gmx.de> Date: Sun, 10 Aug 2025 16:12:31 +0000 Subject: [PATCH 2/2] [clangd] fix review findings --- clang-tools-extra/clangd/CodeCompletionStrings.cpp | 8 ++++---- clang-tools-extra/clangd/SymbolDocumentation.cpp | 2 +- clang-tools-extra/clangd/SymbolDocumentation.h | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index 02c3ea8abd8af..9c4241b54057a 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -112,7 +112,7 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { std::string Doc; if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen && - (isa<ParmVarDecl>(Decl) || isa<TemplateTypeParmDecl>(Decl))) { + isa<ParmVarDecl, TemplateTypeParmDecl>(Decl)) { // Parameters are documented in their declaration context (function or // template function). const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext()); @@ -135,11 +135,11 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { std::string RawDoc; llvm::raw_string_ostream OS(RawDoc); - if (isa<ParmVarDecl>(Decl)) - V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS); + if (auto *PVD = dyn_cast<ParmVarDecl>(&Decl)) + V.parameterDocToString(PVD->getName(), OS); else V.templateTypeParmDocToString( - dyn_cast<TemplateTypeParmDecl>(&Decl)->getName(), OS); + cast<TemplateTypeParmDecl>(&Decl)->getName(), OS); Doc = StringRef(RawDoc).trim().str(); } else { diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp index 9152355cd0f79..9ae1ef3f828e0 100644 --- a/clang-tools-extra/clangd/SymbolDocumentation.cpp +++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp @@ -302,7 +302,7 @@ void SymbolDocCommentVisitor::visitBlockCommandComment( void SymbolDocCommentVisitor::paragraphsToMarkup( markup::Document &Out, - const llvm::SmallVector<const comments::ParagraphComment *> &Paragraphs) + const llvm::SmallVectorImpl<const comments::ParagraphComment *> &Paragraphs) const { if (Paragraphs.empty()) return; diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h index 9d173745965e2..b0d3428dfce20 100644 --- a/clang-tools-extra/clangd/SymbolDocumentation.h +++ b/clang-tools-extra/clangd/SymbolDocumentation.h @@ -199,10 +199,10 @@ class SymbolDocCommentVisitor llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *> FreeParagraphs; - void - paragraphsToMarkup(markup::Document &Out, - const llvm::SmallVector<const comments::ParagraphComment *> - &Paragraphs) const; + void paragraphsToMarkup( + markup::Document &Out, + const llvm::SmallVectorImpl<const comments::ParagraphComment *> + &Paragraphs) const; }; } // namespace clangd _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits