tom-anders created this revision. Herald added subscribers: wenlei, usaxena95, kadircet, arphaman, mgorny. Herald added a project: All. tom-anders published this revision for review. Herald added subscribers: cfe-commits, llvm-commits, MaskRay, ilya-biryukov. Herald added projects: LLVM, clang-tools-extra.
This adds a new class SymbolDocumentation that does basic doxygen parsing. Currently, it's only consumed by Hover, but in future patches we'll probably also want to use it in CodeComplete and SignatureHelp. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D131853 Files: clang-tools-extra/clangd/CMakeLists.txt clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/CodeCompletionStrings.cpp clang-tools-extra/clangd/CodeCompletionStrings.h clang-tools-extra/clangd/Hover.cpp clang-tools-extra/clangd/Hover.h clang-tools-extra/clangd/SymbolDocumentation.cpp clang-tools-extra/clangd/SymbolDocumentation.h clang-tools-extra/clangd/index/Merge.cpp clang-tools-extra/clangd/index/Serialization.cpp clang-tools-extra/clangd/index/Symbol.h clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/index/YAMLSerialization.cpp clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp clang-tools-extra/clangd/unittests/HoverTests.cpp clang-tools-extra/clangd/unittests/IndexTests.cpp clang-tools-extra/clangd/unittests/SerializationTests.cpp clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
Index: llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn =================================================================== --- llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn +++ llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn @@ -117,6 +117,7 @@ "SemanticHighlighting.cpp", "SemanticSelection.cpp", "SourceCode.cpp", + "SymbolDocumentation.cpp" "TUScheduler.cpp", "TidyProvider.cpp", "URI.cpp", Index: clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h =================================================================== --- /dev/null +++ clang-tools-extra/clangd/unittests/SymbolDocumentationMatchers.h @@ -0,0 +1,51 @@ +//===-- SymbolDocumentationMatchers.h ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// GMock matchers for the SymbolDocumentation class +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_MATCHERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_MATCHERS_H +#include "SymbolDocumentation.h" +#include "gmock/gmock.h" + +namespace clang { +namespace clangd { + +template <class S> +testing::Matcher<SymbolDocumentation<S>> +matchesDoc(const SymbolDocumentation<S> &Expected) { + using namespace ::testing; + + std::vector<Matcher<ParameterDocumentation<S>>> ParamMatchers; + for (const auto &P : Expected.Parameters) + ParamMatchers.push_back( + AllOf(Field("Name", &ParameterDocumentation<S>::Name, P.Name), + Field("Description", &ParameterDocumentation<S>::Description, + P.Description))); + + return AllOf( + Field("Brief", &SymbolDocumentation<S>::Brief, Expected.Brief), + Field("Returns", &SymbolDocumentation<S>::Returns, Expected.Returns), + Field("Notes", &SymbolDocumentation<S>::Notes, + ElementsAreArray(Expected.Notes)), + Field("Warnings", &SymbolDocumentation<S>::Warnings, + ElementsAreArray(Expected.Warnings)), + Field("Parameters", &SymbolDocumentation<S>::Parameters, + ElementsAreArray(ParamMatchers)), + Field("Description", &SymbolDocumentation<S>::Description, + Expected.Description), + Field("CommentText", &SymbolDocumentation<S>::CommentText, + Expected.CommentText)); +} + +} // namespace clangd +} // namespace clang + +#endif Index: clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -50,7 +50,7 @@ return (arg.Name + arg.Signature).str() == Label; } MATCHER_P(returnType, D, "") { return arg.ReturnType == D; } -MATCHER_P(doc, D, "") { return arg.Documentation == D; } +MATCHER_P(doc, D, "") { return arg.Documentation.CommentText == D; } MATCHER_P(snippet, S, "") { return (arg.Name + arg.CompletionSnippetSuffix).str() == S; } Index: clang-tools-extra/clangd/unittests/SerializationTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/SerializationTests.cpp +++ clang-tools-extra/clangd/unittests/SerializationTests.cpp @@ -8,6 +8,7 @@ #include "Headers.h" #include "RIFF.h" +#include "SymbolDocumentationMatchers.h" #include "index/Serialization.h" #include "support/Logger.h" #include "clang/Tooling/CompilationDatabase.h" @@ -48,7 +49,22 @@ Line: 1 Column: 1 Flags: 129 -Documentation: 'Foo doc' +Documentation: + Brief: 'Foo brief' + Returns: 'Foo returns' + Description: 'Foo description' + Notes: + - 'Foo note 1' + - 'Foo note 2' + Warnings: + - 'Foo warning 1' + - 'Foo warning 2' + Parameters: + - Name: 'param1' + Description: 'Foo param 1' + - Name: 'param2' + Description: 'Foo param 2' + CommentText: 'Full text would be here' ReturnType: 'int' IncludeHeaders: - Header: 'include1' @@ -141,7 +157,20 @@ EXPECT_THAT(Sym1, qName("clang::Foo1")); EXPECT_EQ(Sym1.Signature, ""); - EXPECT_EQ(Sym1.Documentation, "Foo doc"); + + SymbolDocumentationRef ExpectedDocumentation; + ExpectedDocumentation.Brief = "Foo brief"; + ExpectedDocumentation.Returns = "Foo returns"; + ExpectedDocumentation.Description = "Foo description"; + ExpectedDocumentation.Notes = {"Foo note 1", "Foo note 2"}; + ExpectedDocumentation.Warnings = {"Foo warning 1", "Foo warning 2"}; + ExpectedDocumentation.Parameters = { + {"param1", "Foo param 1"}, + {"param2", "Foo param 2"}, + }; + ExpectedDocumentation.CommentText = "Full text would be here"; + EXPECT_THAT(Sym1.Documentation, matchesDoc(ExpectedDocumentation)); + EXPECT_EQ(Sym1.ReturnType, "int"); EXPECT_EQ(StringRef(Sym1.CanonicalDeclaration.FileURI), "file:///path/foo.h"); EXPECT_EQ(Sym1.Origin, SymbolOrigin::Static); Index: clang-tools-extra/clangd/unittests/IndexTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/IndexTests.cpp +++ clang-tools-extra/clangd/unittests/IndexTests.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Annotations.h" +#include "SymbolDocumentationMatchers.h" #include "SyncAPI.h" #include "TestIndex.h" #include "TestTU.h" @@ -391,7 +392,7 @@ R.References = 2; L.Signature = "()"; // present in left only R.CompletionSnippetSuffix = "{$1:0}"; // present in right only - R.Documentation = "--doc--"; + R.Documentation = SymbolDocumentationRef::descriptionOnly("--doc--"); L.Origin = SymbolOrigin::Preamble; R.Origin = SymbolOrigin::Static; R.Type = "expectedType"; @@ -402,7 +403,8 @@ EXPECT_EQ(M.References, 3u); EXPECT_EQ(M.Signature, "()"); EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}"); - EXPECT_EQ(M.Documentation, "--doc--"); + EXPECT_THAT(M.Documentation, + matchesDoc(SymbolDocumentationRef::descriptionOnly("--doc--"))); EXPECT_EQ(M.Type, "expectedType"); EXPECT_EQ(M.Origin, SymbolOrigin::Preamble | SymbolOrigin::Static | SymbolOrigin::Merge); @@ -546,16 +548,18 @@ Symbol L, R; L.ID = R.ID = SymbolID("x"); L.Definition.FileURI = "file:/x.h"; - R.Documentation = "Forward declarations because x.h is too big to include"; + R.Documentation = SymbolDocumentationRef::descriptionOnly( + "Forward declarations because x.h is too big to include"); for (auto ClassLikeKind : {SymbolKind::Class, SymbolKind::Struct, SymbolKind::Union}) { L.SymInfo.Kind = ClassLikeKind; - EXPECT_EQ(mergeSymbol(L, R).Documentation, ""); + ASSERT_TRUE(mergeSymbol(L, R).Documentation.empty()); } L.SymInfo.Kind = SymbolKind::Function; - R.Documentation = "Documentation from non-class symbols should be included"; - EXPECT_EQ(mergeSymbol(L, R).Documentation, R.Documentation); + R.Documentation = SymbolDocumentationRef::descriptionOnly( + "Documentation from non-class symbols should be included"); + EXPECT_THAT(mergeSymbol(L, R).Documentation, matchesDoc(R.Documentation)); } MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") { Index: clang-tools-extra/clangd/unittests/HoverTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/HoverTests.cpp +++ clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -10,6 +10,7 @@ #include "Annotations.h" #include "Config.h" #include "Hover.h" +#include "SymbolDocumentationMatchers.h" #include "TestIndex.h" #include "TestTU.h" #include "index/MemIndex.h" @@ -43,7 +44,8 @@ HI.NamespaceScope = ""; HI.Name = "foo"; HI.Kind = index::SymbolKind::Function; - HI.Documentation = "Best foo ever."; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Best foo ever."); HI.Definition = "void foo()"; HI.ReturnType = "void"; HI.Type = "void ()"; @@ -60,7 +62,8 @@ HI.NamespaceScope = "ns1::ns2::"; HI.Name = "foo"; HI.Kind = index::SymbolKind::Function; - HI.Documentation = "Best foo ever."; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Best foo ever."); HI.Definition = "void foo()"; HI.ReturnType = "void"; HI.Type = "void ()"; @@ -669,7 +672,8 @@ HI.Definition = "template <> class Foo<int *>"; // FIXME: Maybe force instantiation to make use of real template // pattern. - HI.Documentation = "comment from primary"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("comment from primary"); }}, {// Template Type Parameter R"cpp( @@ -721,7 +725,8 @@ HI.NamespaceScope = ""; HI.Definition = "float y()"; HI.LocalScope = "X::"; - HI.Documentation = "Trivial accessor for `Y`."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Trivial accessor for `Y`."); HI.Type = "float ()"; HI.ReturnType = "float"; HI.Parameters.emplace(); @@ -737,7 +742,8 @@ HI.NamespaceScope = ""; HI.Definition = "void setY(float v)"; HI.LocalScope = "X::"; - HI.Documentation = "Trivial setter for `Y`."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Trivial setter for `Y`."); HI.Type = "void (float)"; HI.ReturnType = "void"; HI.Parameters.emplace(); @@ -756,7 +762,8 @@ HI.NamespaceScope = ""; HI.Definition = "X &setY(float v)"; HI.LocalScope = "X::"; - HI.Documentation = "Trivial setter for `Y`."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Trivial setter for `Y`."); HI.Type = "X &(float)"; HI.ReturnType = "X &"; HI.Parameters.emplace(); @@ -776,7 +783,8 @@ HI.NamespaceScope = ""; HI.Definition = "void setY(float v)"; HI.LocalScope = "X::"; - HI.Documentation = "Trivial setter for `Y`."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Trivial setter for `Y`."); HI.Type = "void (float)"; HI.ReturnType = "void"; HI.Parameters.emplace(); @@ -1068,7 +1076,7 @@ EXPECT_EQ(H->LocalScope, Expected.LocalScope); EXPECT_EQ(H->Name, Expected.Name); EXPECT_EQ(H->Kind, Expected.Kind); - EXPECT_EQ(H->Documentation, Expected.Documentation); + ASSERT_THAT(H->Documentation, matchesDoc(Expected.Documentation)); EXPECT_EQ(H->Definition, Expected.Definition); EXPECT_EQ(H->Type, Expected.Type); EXPECT_EQ(H->ReturnType, Expected.ReturnType); @@ -1345,7 +1353,8 @@ HI.NamespaceScope = ""; HI.Type = "void (int)"; HI.Definition = "void foo(int)"; - HI.Documentation = "Function definition via pointer"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Function definition via pointer"); HI.ReturnType = "void"; HI.Parameters = { {{"int"}, llvm::None, llvm::None}, @@ -1364,7 +1373,8 @@ HI.NamespaceScope = ""; HI.Type = "int (int)"; HI.Definition = "int foo(int)"; - HI.Documentation = "Function declaration via call"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Function declaration via call"); HI.ReturnType = "int"; HI.Parameters = { {{"int"}, llvm::None, llvm::None}, @@ -1512,7 +1522,8 @@ HI.NamespaceScope = ""; HI.Definition = "typedef int Foo"; HI.Type = "int"; - HI.Documentation = "Typedef"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Typedef"); }}, { R"cpp(// Typedef with embedded definition @@ -1527,7 +1538,8 @@ HI.NamespaceScope = ""; HI.Definition = "typedef struct Bar Foo"; HI.Type = "struct Bar"; - HI.Documentation = "Typedef with embedded definition"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Typedef with embedded definition"); }}, { R"cpp(// Namespace @@ -1603,10 +1615,18 @@ HI.Kind = index::SymbolKind::Class; HI.NamespaceScope = ""; HI.Definition = "class Foo {}"; - HI.Documentation = "Forward class declaration"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "Forward class declaration"); }}, { - R"cpp(// Function declaration + R"cpp( + /// \brief Function declaration + /// \details Some details + /// \throws std::runtime_error sometimes + /// \param x doc for x + /// \warning Watch out! + /// \note note1 \note note2 + /// \return Nothing void foo(); void g() { [[f^oo]](); } void foo() {} @@ -1617,7 +1637,22 @@ HI.NamespaceScope = ""; HI.Type = "void ()"; HI.Definition = "void foo()"; - HI.Documentation = "Function declaration"; + HI.Documentation.Brief = "Function declaration"; + HI.Documentation.Description = "\\details Some details\n\n\\throws " + "std::runtime_error sometimes"; + HI.Documentation.Parameters = { + {"x", "doc for x"}, + }; + HI.Documentation.Returns = "Nothing"; + HI.Documentation.Notes = {"note1", "note2"}; + HI.Documentation.Warnings = {"Watch out!"}; + HI.Documentation.CommentText = R"(\brief Function declaration +\details Some details +\throws std::runtime_error sometimes +\param x doc for x +\warning Watch out! +\note note1 \note note2 +\return Nothing)"; HI.ReturnType = "void"; HI.Parameters = std::vector<HoverInfo::Param>{}; }}, @@ -1635,7 +1670,8 @@ HI.Kind = index::SymbolKind::Enum; HI.NamespaceScope = ""; HI.Definition = "enum Hello {}"; - HI.Documentation = "Enum declaration"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Enum declaration"); }}, { R"cpp(// Enumerator @@ -1687,7 +1723,8 @@ HI.NamespaceScope = ""; HI.Type = "int"; HI.Definition = "static int hey = 10"; - HI.Documentation = "Global variable"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Global variable"); // FIXME: Value shouldn't be set in this case HI.Value = "10 (0xa)"; }}, @@ -1739,7 +1776,8 @@ HI.NamespaceScope = ""; HI.Type = "int ()"; HI.Definition = "template <> int foo<int>()"; - HI.Documentation = "Templated function"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("Templated function"); HI.ReturnType = "int"; HI.Parameters = std::vector<HoverInfo::Param>{}; // FIXME: We should populate template parameters with arguments in @@ -1776,7 +1814,8 @@ HI.Definition = "void indexSymbol()"; HI.ReturnType = "void"; HI.Parameters = std::vector<HoverInfo::Param>{}; - HI.Documentation = "comment from index"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("comment from index"); }}, { R"cpp(// Simple initialization with auto @@ -1945,7 +1984,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "auto function return with trailing type"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "auto function return with trailing type"); }}, { R"cpp(// trailing return type @@ -1958,7 +1998,8 @@ HI.Name = "decltype"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "trailing return type"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "trailing return type"); }}, { R"cpp(// auto in function return @@ -1971,7 +2012,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "auto in function return"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "auto in function return"); }}, { R"cpp(// auto& in function return @@ -1985,7 +2027,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "auto& in function return"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "auto& in function return"); }}, { R"cpp(// auto* in function return @@ -1999,7 +2042,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "auto* in function return"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "auto* in function return"); }}, { R"cpp(// const auto& in function return @@ -2013,7 +2057,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "const auto& in function return"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "const auto& in function return"); }}, { R"cpp(// decltype(auto) in function return @@ -2026,7 +2071,8 @@ HI.Name = "decltype"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = "decltype(auto) in function return"; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "decltype(auto) in function return"); }}, { R"cpp(// decltype(auto) reference in function return @@ -2116,8 +2162,8 @@ HI.Name = "decltype"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "Bar"; - HI.Documentation = - "decltype of function with trailing return type."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "decltype of function with trailing return type."); }}, { R"cpp(// decltype of var with decltype. @@ -2187,7 +2233,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "cls_type // aka: cls"; - HI.Documentation = "auto on alias"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("auto on alias"); }}, { R"cpp(// auto on alias @@ -2199,7 +2246,8 @@ HI.Name = "auto"; HI.Kind = index::SymbolKind::TypeAlias; HI.Definition = "templ<int>"; - HI.Documentation = "auto on alias"; + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly("auto on alias"); }}, { R"cpp(// Undeduced auto declaration @@ -2290,7 +2338,8 @@ HI.Kind = index::SymbolKind::Struct; HI.NamespaceScope = ""; HI.Name = "cls<cls<cls<int>>>"; - HI.Documentation = "type of nested templates."; + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + "type of nested templates."); }}, { R"cpp(// type with decltype @@ -2584,13 +2633,15 @@ HI.Name = "nonnull"; HI.Kind = index::SymbolKind::Unknown; // FIXME: no suitable value HI.Definition = "__attribute__((nonnull))"; - HI.Documentation = Attr::getDocumentation(attr::NonNull).str(); + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + Attr::getDocumentation(attr::NonNull).str()); }}, }; // Create a tiny index, so tests above can verify documentation is fetched. Symbol IndexSym = func("indexSymbol"); - IndexSym.Documentation = "comment from index"; + IndexSym.Documentation = + SymbolDocumentationRef::descriptionOnly("comment from index"); SymbolSlab::Builder Symbols; Symbols.insert(IndexSym); auto Index = @@ -2623,7 +2674,7 @@ EXPECT_EQ(H->LocalScope, Expected.LocalScope); EXPECT_EQ(H->Name, Expected.Name); EXPECT_EQ(H->Kind, Expected.Kind); - EXPECT_EQ(H->Documentation, Expected.Documentation); + ASSERT_THAT(H->Documentation, matchesDoc(Expected.Documentation)); EXPECT_EQ(H->Definition, Expected.Definition); EXPECT_EQ(H->Type, Expected.Type); EXPECT_EQ(H->ReturnType, Expected.ReturnType); @@ -2647,7 +2698,8 @@ auto AST = TU.build(); Symbol IndexSym; IndexSym.ID = getSymbolID(&findDecl(AST, "X")); - IndexSym.Documentation = "comment from index"; + IndexSym.Documentation = + SymbolDocumentationRef::descriptionOnly("comment from index"); SymbolSlab::Builder Symbols; Symbols.insert(IndexSym); auto Index = @@ -2656,7 +2708,7 @@ for (const auto &P : T.points()) { auto H = getHover(AST, P, format::getLLVMStyle(), Index.get()); ASSERT_TRUE(H); - EXPECT_EQ(H->Documentation, IndexSym.Documentation); + ASSERT_THAT(H->Documentation.toRef(), matchesDoc(IndexSym.Documentation)); } } @@ -2681,7 +2733,8 @@ for (const auto &P : T.points()) { auto H = getHover(AST, P, format::getLLVMStyle(), nullptr); ASSERT_TRUE(H); - EXPECT_EQ(H->Documentation, "doc"); + ASSERT_THAT(H->Documentation, + matchesDoc(SymbolDocumentationOwned::descriptionOnly("doc"))); } } @@ -2719,7 +2772,9 @@ for (const auto &P : T.points(Comment)) { auto H = getHover(AST, P, format::getLLVMStyle(), nullptr); ASSERT_TRUE(H); - EXPECT_EQ(H->Documentation, Comment); + ASSERT_THAT( + H->Documentation, + matchesDoc(SymbolDocumentationOwned::descriptionOnly(Comment))); } } } @@ -2751,7 +2806,14 @@ {{"typename"}, std::string("T"), llvm::None}, {{"typename"}, std::string("C"), std::string("bool")}, }; - HI.Documentation = "documentation"; + HI.Documentation.Brief = "brief"; + HI.Documentation.Description = "details"; + HI.Documentation.Parameters = { + {"Parameters", "should be ignored for classes"}}; + HI.Documentation.Returns = "Returns should be ignored for classes"; + HI.Documentation.Notes = {"note1", "note2"}; + HI.Documentation.Warnings = {"warning1", "warning2"}; + HI.Documentation.CommentText = "Not used for Hover presentation"; HI.Definition = "template <typename T, typename C = bool> class Foo {}"; HI.Name = "foo"; @@ -2759,8 +2821,17 @@ }, R"(class foo +brief Size: 10 bytes -documentation +details + +Warnings: +- warning1 +- warning2 + +Notes: +- note1 +- note2 template <typename T, typename C = bool> class Foo {})", }, @@ -2779,17 +2850,37 @@ HI.Parameters->push_back(P); P.Default = "default"; HI.Parameters->push_back(P); + HI.Documentation.Brief = "brief"; + HI.Documentation.Description = "details"; + HI.Documentation.Parameters = { + {"foo", "param doc"}, + {"bar", "doc for parameter not in the signature"}}; + HI.Documentation.Returns = "doc for return"; + HI.Documentation.Notes = {"note1", "note2"}; + HI.Documentation.Warnings = {"warning1", "warning2"}; + HI.Documentation.CommentText = "Not used for Hover presentation"; HI.NamespaceScope = "ns::"; HI.Definition = "ret_type foo(params) {}"; }, "function foo\n" "\n" - "â ret_type (aka can_ret_type)\n" + "brief\n" + "â ret_type (aka can_ret_type): doc for return\n" "Parameters:\n" "- \n" "- type (aka can_type)\n" - "- type foo (aka can_type)\n" + "- type foo (aka can_type): param doc\n" "- type foo = default (aka can_type)\n" + "- bar: doc for parameter not in the signature\n" + "details\n" + "\n" + "Warnings:\n" + "- warning1\n" + "- warning2\n" + "\n" + "Notes:\n" + "- note1\n" + "- note2\n" "\n" "// In namespace ns\n" "ret_type foo(params) {}", @@ -3146,7 +3237,7 @@ template <typename T> struct S { - // Foo bar baz + /// Foo bar baz friend auto operator<=>(S, S) = default; }; static_assert(S<void>() =^= S<void>()); @@ -3156,7 +3247,10 @@ TU.ExtraArgs.push_back("-std=c++20"); auto AST = TU.build(); auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr); - EXPECT_EQ(HI->Documentation, "Foo bar baz"); + + ASSERT_THAT( + HI->Documentation, + matchesDoc(SymbolDocumentationOwned::descriptionOnly("Foo bar baz"))); } TEST(Hover, ForwardStructNoCrash) { Index: clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp +++ clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "CodeCompletionStrings.h" +#include "SymbolDocumentationMatchers.h" #include "TestTU.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "gmock/gmock.h" @@ -57,12 +58,121 @@ "Annotation: Ano\n\nIs this brief?"); } -TEST_F(CompletionStringTest, GetDeclCommentBadUTF8) { +TEST_F(CompletionStringTest, GetDeclDocumentationBadUTF8) { // <ff> is not a valid byte here, should be replaced by encoded <U+FFFD>. - auto TU = TestTU::withCode("/*x\xffy*/ struct X;"); + const std::string Code = llvm::formatv(R"cpp( + /// \brief {0} + /// \details {0} + /// \param {0} {0} + /// \warning {0} + /// \note {0} + /// \return {0} + struct X; + )cpp", + "x\xffy"); + + auto TU = TestTU::withCode(Code); auto AST = TU.build(); - EXPECT_EQ("x\xef\xbf\xbdy", - getDeclComment(AST.getASTContext(), findDecl(AST, "X"))); + + const std::string Utf8Replacement = "x\xef\xbf\xbdy"; + SymbolDocumentationOwned ExpectedDoc; + ExpectedDoc.Brief = Utf8Replacement; + ExpectedDoc.Returns = Utf8Replacement; + ExpectedDoc.Parameters = {{Utf8Replacement, Utf8Replacement}}; + ExpectedDoc.Notes = {Utf8Replacement}; + ExpectedDoc.Warnings = {Utf8Replacement}; + ExpectedDoc.Description = {"\\details " + Utf8Replacement}; + ExpectedDoc.CommentText = llvm::formatv(R"(\brief {0} +\details {0} +\param {0} {0} +\warning {0} +\note {0} +\return {0})", Utf8Replacement); + + EXPECT_THAT(getDeclDocumentation(AST.getASTContext(), findDecl(AST, "X")), + matchesDoc(ExpectedDoc)); +} + +TEST_F(CompletionStringTest, DoxygenParsing) { + struct { + const char *const Code; + const std::function<void(SymbolDocumentationOwned &)> ExpectedBuilder; + } Cases[] = { + {R"cpp( + // Hello world + void foo(); + )cpp", + [](SymbolDocumentationOwned &Doc) { Doc.Description = "Hello world"; }}, + {R"cpp( + /*! + * \brief brief + * \details details + */ + void foo(); + )cpp", + [](SymbolDocumentationOwned &Doc) { + Doc.Brief = "brief"; + Doc.Description = "\\details details"; + }}, + {R"cpp( + /** + * @brief brief + * @details details + * @see somewhere else + */ + void foo(); + )cpp", + [](SymbolDocumentationOwned &Doc) { + Doc.Brief = "brief"; + Doc.Description = "@details details\n\n@see somewhere else"; + }}, + {R"cpp( + /*! + * @brief brief + * @details details + * @param foo foodoc + * @throws ball at hoop + * @note note1 + * @warning warning1 + * @note note2 + * @warning warning2 + * @param bar bardoc + * @return something + */ + void foo(); + )cpp", + [](SymbolDocumentationOwned &Doc) { + Doc.Brief = "brief"; + Doc.Description = "@details details\n\n@throws ball at hoop"; + Doc.Parameters = {{"foo", "foodoc"}, {"bar", "bardoc"}}; + Doc.Warnings = {"warning1", "warning2"}; + Doc.Notes = {"note1", "note2"}; + Doc.Returns = "something"; + }}, + {R"cpp( + /// @brief Here's \b bold \e italic and \p code + int foo; + )cpp", + [](SymbolDocumentationOwned &Doc) { + Doc.Brief = "Here's **bold** *italic* and `code`"; + }}}; + + for (const auto &Case : Cases) { + SCOPED_TRACE(Case.Code); + + auto TU = TestTU::withCode(Case.Code); + auto AST = TU.build(); + auto &Ctx = AST.getASTContext(); + const auto &Decl = findDecl(AST, "foo"); + + SymbolDocumentationOwned ExpectedDoc; + ExpectedDoc.CommentText = + getCompletionComment(Ctx, &Decl) + ->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); + Case.ExpectedBuilder(ExpectedDoc); + + EXPECT_THAT(getDeclDocumentation(Ctx, Decl), matchesDoc(ExpectedDoc)); + } } TEST_F(CompletionStringTest, MultipleAnnotations) { Index: clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2330,9 +2330,9 @@ TEST(SignatureHelpTest, IndexDocumentation) { Symbol Foo0 = sym("foo", index::SymbolKind::Function, "@F@\\0#"); - Foo0.Documentation = "doc from the index"; + Foo0.Documentation.CommentText = "doc from the index"; Symbol Foo1 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#"); - Foo1.Documentation = "doc from the index"; + Foo1.Documentation.CommentText = "doc from the index"; Symbol Foo2 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#I#"); StringRef Sig0 = R"cpp( Index: clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp =================================================================== --- clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp +++ clang-tools-extra/clangd/test/index-serialization/Inputs/sample.cpp @@ -3,6 +3,11 @@ // This introduces a symbol, a reference and a relation. struct Bar : public Foo { - // This introduces an OverriddenBy relation by implementing Foo::Func. + /// \brief This introduces an OverriddenBy relation by implementing Foo::Func. + /// \details And it also introduces some doxygen! + /// \param foo bar + /// \warning !!! + /// \note a note + /// \return nothing void Func() override {} }; Index: clang-tools-extra/clangd/index/YAMLSerialization.cpp =================================================================== --- clang-tools-extra/clangd/index/YAMLSerialization.cpp +++ clang-tools-extra/clangd/index/YAMLSerialization.cpp @@ -30,6 +30,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Symbol::IncludeHeaderWithReferences) LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Ref) +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::ParameterDocumentationRef) namespace { using RefBundle = @@ -59,11 +60,13 @@ using clang::clangd::FileDigest; using clang::clangd::IncludeGraph; using clang::clangd::IncludeGraphNode; +using clang::clangd::ParameterDocumentationRef; using clang::clangd::Ref; using clang::clangd::RefKind; using clang::clangd::Relation; using clang::clangd::RelationKind; using clang::clangd::Symbol; +using clang::clangd::SymbolDocumentationRef; using clang::clangd::SymbolID; using clang::clangd::SymbolLocation; using clang::index::SymbolInfo; @@ -174,6 +177,28 @@ } }; +template <> struct MappingTraits<ParameterDocumentationRef> { + static void mapping(IO &IO, ParameterDocumentationRef &P) { + IO.mapRequired("Name", P.Name); + IO.mapRequired("Description", P.Description); + } +}; + +template <> struct MappingTraits<SymbolDocumentationRef> { + static void mapping(IO &IO, SymbolDocumentationRef &Doc) { + IO.mapOptional("Brief", Doc.Brief); + IO.mapOptional("Returns", Doc.Returns); + + IO.mapOptional("Notes", Doc.Notes); + IO.mapOptional("Warnings", Doc.Warnings); + + IO.mapOptional("Parameters", Doc.Parameters); + + IO.mapOptional("Description", Doc.Description); + IO.mapOptional("CommentText", Doc.CommentText); + } +}; + template <> struct MappingTraits<Symbol> { static void mapping(IO &IO, Symbol &Sym) { MappingNormalization<NormalizedSymbolID, SymbolID> NSymbolID(IO, Sym.ID); Index: clang-tools-extra/clangd/index/SymbolCollector.cpp =================================================================== --- clang-tools-extra/clangd/index/SymbolCollector.cpp +++ clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -912,16 +912,17 @@ *ASTCtx, *PP, CodeCompletionContext::CCC_Symbol, *CompletionAllocator, *CompletionTUInfo, /*IncludeBriefComments*/ false); - std::string Documentation = - formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion, - /*CommentsFromHeaders=*/true)); + + const SymbolDocumentationOwned Documentation = + getDocumentation(Ctx, SymbolCompletion, /*CommentsFromHeaders=*/true); + if (!(S.Flags & Symbol::IndexedForCodeCompletion)) { if (Opts.StoreAllDocumentation) - S.Documentation = Documentation; + S.Documentation = Documentation.toRef(); Symbols.insert(S); return Symbols.find(S.ID); } - S.Documentation = Documentation; + S.Documentation = Documentation.toRef(); std::string Signature; std::string SnippetSuffix; getSignature(*CCS, &Signature, &SnippetSuffix); Index: clang-tools-extra/clangd/index/Symbol.h =================================================================== --- clang-tools-extra/clangd/index/Symbol.h +++ clang-tools-extra/clangd/index/Symbol.h @@ -9,6 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_H +#include "SymbolDocumentation.h" #include "index/SymbolID.h" #include "index/SymbolLocation.h" #include "index/SymbolOrigin.h" @@ -73,7 +74,7 @@ /// Only set when the symbol is indexed for completion. llvm::StringRef CompletionSnippetSuffix; /// Documentation including comment for the symbol declaration. - llvm::StringRef Documentation; + SymbolDocumentationRef Documentation; /// Type when this symbol is used in an expression. (Short display form). /// e.g. return type of a function, or type of a variable. /// Only set when the symbol is indexed for completion. @@ -150,7 +151,20 @@ CB(S.TemplateSpecializationArgs); CB(S.Signature); CB(S.CompletionSnippetSuffix); - CB(S.Documentation); + + CB(S.Documentation.Brief); + CB(S.Documentation.Returns); + for (auto &Note : S.Documentation.Notes) + CB(Note); + for (auto &Warning : S.Documentation.Warnings) + CB(Warning); + for (auto &ParamDoc : S.Documentation.Parameters) { + CB(ParamDoc.Name); + CB(ParamDoc.Description); + } + CB(S.Documentation.Description); + CB(S.Documentation.CommentText); + CB(S.ReturnType); CB(S.Type); auto RawCharPointerCB = [&CB](const char *&P) { Index: clang-tools-extra/clangd/index/Serialization.cpp =================================================================== --- clang-tools-extra/clangd/index/Serialization.cpp +++ clang-tools-extra/clangd/index/Serialization.cpp @@ -283,6 +283,57 @@ return Loc; } +void writeSymbolDocumentation(const SymbolDocumentationRef &Doc, + const StringTableOut &Strings, + llvm::raw_ostream &OS) { + writeVar(Strings.index(Doc.Brief), OS); + writeVar(Strings.index(Doc.Returns), OS); + + writeVar(Doc.Notes.size(), OS); + for (const auto &Note : Doc.Notes) + writeVar(Strings.index(Note), OS); + + writeVar(Doc.Warnings.size(), OS); + for (const auto &Warning : Doc.Warnings) + writeVar(Strings.index(Warning), OS); + + writeVar(Doc.Parameters.size(), OS); + for (const auto &ParamDoc : Doc.Parameters) { + writeVar(Strings.index(ParamDoc.Name), OS); + writeVar(Strings.index(ParamDoc.Description), OS); + } + + writeVar(Strings.index(Doc.Description), OS); + writeVar(Strings.index(Doc.CommentText), OS); +} + +SymbolDocumentationRef +readSymbolDocumentation(Reader &Data, llvm::ArrayRef<llvm::StringRef> Strings) { + SymbolDocumentationRef Doc; + Doc.Brief = Data.consumeString(Strings); + Doc.Returns = Data.consumeString(Strings); + + if (!Data.consumeSize(Doc.Notes)) + return Doc; + for (auto &Note : Doc.Notes) + Note = Data.consumeString(Strings); + + if (!Data.consumeSize(Doc.Warnings)) + return Doc; + for (auto &Warning : Doc.Warnings) + Warning = Data.consumeString(Strings); + + if (!Data.consumeSize(Doc.Parameters)) + return Doc; + for (auto &ParamDoc : Doc.Parameters) + ParamDoc = {Data.consumeString(Strings), Data.consumeString(Strings)}; + + Doc.Description = Data.consumeString(Strings); + Doc.CommentText = Data.consumeString(Strings); + + return Doc; +} + IncludeGraphNode readIncludeGraphNode(Reader &Data, llvm::ArrayRef<llvm::StringRef> Strings) { IncludeGraphNode IGN; @@ -325,7 +376,7 @@ OS.write(static_cast<uint8_t>(Sym.Flags)); writeVar(Strings.index(Sym.Signature), OS); writeVar(Strings.index(Sym.CompletionSnippetSuffix), OS); - writeVar(Strings.index(Sym.Documentation), OS); + writeSymbolDocumentation(Sym.Documentation, Strings, OS); writeVar(Strings.index(Sym.ReturnType), OS); writeVar(Strings.index(Sym.Type), OS); @@ -354,7 +405,7 @@ Sym.Origin = Origin; Sym.Signature = Data.consumeString(Strings); Sym.CompletionSnippetSuffix = Data.consumeString(Strings); - Sym.Documentation = Data.consumeString(Strings); + Sym.Documentation = readSymbolDocumentation(Data, Strings); Sym.ReturnType = Data.consumeString(Strings); Sym.Type = Data.consumeString(Strings); if (!Data.consumeSize(Sym.IncludeHeaders)) @@ -455,7 +506,7 @@ // The current versioning scheme is simple - non-current versions are rejected. // If you make a breaking change, bump this version number to invalidate stored // data. Later we may want to support some backward compatibility. -constexpr static uint32_t Version = 17; +constexpr static uint32_t Version = 18; llvm::Expected<IndexFileIn> readRIFF(llvm::StringRef Data, SymbolOrigin Origin) { Index: clang-tools-extra/clangd/index/Merge.cpp =================================================================== --- clang-tools-extra/clangd/index/Merge.cpp +++ clang-tools-extra/clangd/index/Merge.cpp @@ -227,7 +227,7 @@ S.Signature = O.Signature; if (S.CompletionSnippetSuffix == "") S.CompletionSnippetSuffix = O.CompletionSnippetSuffix; - if (S.Documentation == "") { + if (S.Documentation.empty()) { // Don't accept documentation from bare forward class declarations, if there // is a definition and it didn't provide one. S is often an undocumented // class, and O is a non-canonical forward decl preceded by an irrelevant Index: clang-tools-extra/clangd/SymbolDocumentation.h =================================================================== --- /dev/null +++ clang-tools-extra/clangd/SymbolDocumentation.h @@ -0,0 +1,101 @@ +//===--- SymbolDocumentation.h ==---------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Class to parse doxygen comments into a flat structure for consumption +// in e.g. Hover and Code Completion +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" +#include "clang/AST/CommentVisitor.h" + +namespace clang { +namespace clangd { + +template <class String> struct ParameterDocumentation { + String Name; + String Description; + + ParameterDocumentation<llvm::StringRef> toRef() const; + ParameterDocumentation<std::string> toOwned() const; +}; + +using ParameterDocumentationRef = ParameterDocumentation<llvm::StringRef>; +using ParameterDocumentationOwned = ParameterDocumentation<std::string>; + +/// @brief Represents a parsed doxygen comment. +/// @details Currently there's special handling for the "brief", "param" +/// "returns", "note" and "warning" commands. The content of all other +/// paragraphs will be appended to the #Description field. +/// If you're only interested in the full comment, but with comment +/// markers stripped, use the #CommentText field. +/// \tparam String When built from a declaration, we're building the strings +/// by ourselves, so in this case String==std::string. +/// However, when storing the contents of this class in the index, we need to +/// use llvm::StringRef. To connvert between std::string and llvm::StringRef +/// versions of this class, use toRef() and toOwned(). +template <class String> class SymbolDocumentation { +public: + friend class CommentToSymbolDocumentation; + + static SymbolDocumentation<String> descriptionOnly(String &&Description) { + SymbolDocumentation<String> Doc; + Doc.Description = Description; + Doc.CommentText = Description; + return Doc; + } + + /// Constructs with all fields as empty strings/vectors. + SymbolDocumentation() = default; + + SymbolDocumentation<llvm::StringRef> toRef() const; + SymbolDocumentation<std::string> toOwned() const; + + bool empty() const { return CommentText.empty(); } + + /// Paragraph of the "brief" command. + String Brief; + + /// Paragraph of the "return" command. + String Returns; + + /// Paragraph(s) of the "note" command(s) + llvm::SmallVector<String, 1> Notes; + /// Paragraph(s) of the "warning" command(s) + llvm::SmallVector<String, 1> Warnings; + + /// Parsed paragaph(s) of the "param" comamnd(s) + llvm::SmallVector<ParameterDocumentation<String>> Parameters; + + /// All the paragraphs we don't have any special handling for, + /// e.g. "details". + String Description; + + /// The full documentation comment with comment markers stripped. + /// See clang::RawComment::getFormattedText() for the detailed + /// explanation of how the comment text is transformed. + String CommentText; +}; + +using SymbolDocumentationOwned = SymbolDocumentation<std::string>; +using SymbolDocumentationRef = SymbolDocumentation<llvm::StringRef>; + +/// @param RC the comment to parse +/// @param D the declaration that \p RC belongs to +/// @return parsed doxgen documentation. +SymbolDocumentationOwned +parseDoxygenComment(const RawComment &RC, const ASTContext &Ctx, const Decl *D); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H Index: clang-tools-extra/clangd/SymbolDocumentation.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clangd/SymbolDocumentation.cpp @@ -0,0 +1,205 @@ +//===--- SymbolDocumentation.cpp ==-------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "SymbolDocumentation.h" +#include "clang/AST/CommentVisitor.h" +#include "llvm/Support/JSON.h" + +namespace clang { +namespace clangd { + +void ensureUTF8(std::string &Str) { + if (!llvm::json::isUTF8(Str)) + Str = llvm::json::fixUTF8(Str); +} + +class BlockCommentToString + : public comments::ConstCommentVisitor<BlockCommentToString> { +public: + BlockCommentToString(std::string &Out, const ASTContext &Ctx) + : Out(Out), Ctx(Ctx) {} + + void visitParagraphComment(const comments::ParagraphComment *C) { + for (const auto *Child = C->child_begin(); Child != C->child_end(); + ++Child) { + visit(*Child); + } + } + + void visitBlockCommandComment(const comments::BlockCommandComment *B) { + Out << (B->getCommandMarker() == (comments::CommandMarkerKind::CMK_At) + ? '@' + : '\\') + << B->getCommandName(Ctx.getCommentCommandTraits()); + + visit(B->getParagraph()); + } + + void visitTextComment(const comments::TextComment *C) { + // If this is the very first node, the paragraph has no doxygen command, + // so there will be a leading space -> Trim it + // Otherwise just trim trailing space + if (Out.str().empty()) + Out << C->getText().trim(); + else + Out << C->getText().rtrim(); + } + + void visitInlineCommandComment(const comments::InlineCommandComment *C) { + const std::string SurroundWith = [C] { + switch (C->getRenderKind()) { + case comments::InlineCommandComment::RenderKind::RenderMonospaced: + return "`"; + case comments::InlineCommandComment::RenderKind::RenderBold: + return "**"; + case comments::InlineCommandComment::RenderKind::RenderEmphasized: + return "*"; + default: + return ""; + } + }(); + + Out << " " << SurroundWith; + for (unsigned I = 0; I < C->getNumArgs(); ++I) { + Out << C->getArgText(I); + } + Out << SurroundWith; + } + +private: + llvm::raw_string_ostream Out; + const ASTContext &Ctx; +}; + +class CommentToSymbolDocumentation + : public comments::ConstCommentVisitor<CommentToSymbolDocumentation> { +public: + CommentToSymbolDocumentation(const RawComment &RC, const ASTContext &Ctx, + const Decl *D, SymbolDocumentationOwned &Doc) + : FullComment(RC.parse(Ctx, nullptr, D)), Output(Doc), Ctx(Ctx) { + + Doc.CommentText = + RC.getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); + + for (auto *Block : FullComment->getBlocks()) { + visit(Block); + } + } + + void visitBlockCommandComment(const comments::BlockCommandComment *B) { + const llvm::StringRef CommandName = + B->getCommandName(Ctx.getCommentCommandTraits()); + + // 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 (CommandName == "brief") { + BlockCommentToString(Output.Brief, Ctx).visit(B->getParagraph()); + } else if (CommandName == "return") { + BlockCommentToString(Output.Returns, Ctx).visit(B->getParagraph()); + } else if (CommandName == "warning") { + BlockCommentToString(Output.Warnings.emplace_back(), Ctx) + .visit(B->getParagraph()); + } else if (CommandName == "note") { + BlockCommentToString(Output.Notes.emplace_back(), Ctx) + .visit(B->getParagraph()); + } else { + if (!Output.Description.empty()) + Output.Description += "\n\n"; + + BlockCommentToString(Output.Description, Ctx).visit(B); + } + } + + void visitParagraphComment(const comments::ParagraphComment *P) { + BlockCommentToString(Output.Description, Ctx).visit(P); + } + + void visitParamCommandComment(const comments::ParamCommandComment *P) { + if (P->hasParamName() && P->hasNonWhitespaceParagraph()) { + ParameterDocumentationOwned Doc; + Doc.Name = P->getParamNameAsWritten().str(); + BlockCommentToString(Doc.Description, Ctx).visit(P->getParagraph()); + Output.Parameters.push_back(std::move(Doc)); + } + } + +private: + comments::FullComment *FullComment; + SymbolDocumentationOwned &Output; + const ASTContext &Ctx; +}; + +SymbolDocumentationOwned parseDoxygenComment(const RawComment &RC, + const ASTContext &Ctx, + const Decl *D) { + SymbolDocumentationOwned Doc; + CommentToSymbolDocumentation(RC, Ctx, D, Doc); + + // Clang requires source to be UTF-8, but doesn't enforce this in comments. + ensureUTF8(Doc.Brief); + ensureUTF8(Doc.Returns); + for (auto &Note : Doc.Notes) + ensureUTF8(Note); + for (auto &Warning : Doc.Warnings) + ensureUTF8(Warning); + for (auto &Param : Doc.Parameters) { + ensureUTF8(Param.Name); + ensureUTF8(Param.Description); + } + ensureUTF8(Doc.Description); + ensureUTF8(Doc.CommentText); + + return Doc; +} + +template struct ParameterDocumentation<std::string>; +template struct ParameterDocumentation<llvm::StringRef>; + +template <class S1, class S2> +SymbolDocumentation<S1> convert(const SymbolDocumentation<S2> &In) { + SymbolDocumentation<S1> Doc; + + Doc.Brief = In.Brief; + Doc.Returns = In.Returns; + + Doc.Notes.reserve(In.Notes.size()); + for (const auto &Note : In.Notes) { + Doc.Notes.emplace_back(Note); + } + + Doc.Warnings.reserve(In.Warnings.size()); + for (const auto &Warning : In.Warnings) { + Doc.Warnings.emplace_back(Warning); + } + + Doc.Parameters.reserve(In.Parameters.size()); + for (const auto &ParamDoc : In.Parameters) { + Doc.Parameters.emplace_back(ParameterDocumentation<S1>{ + S1(ParamDoc.Name), S1(ParamDoc.Description)}); + } + + Doc.Description = In.Description; + Doc.CommentText = In.CommentText; + + return Doc; +} + +template <> SymbolDocumentationRef SymbolDocumentationOwned::toRef() const { + return convert<llvm::StringRef>(*this); +} + +template <> SymbolDocumentationOwned SymbolDocumentationRef::toOwned() const { + return convert<std::string>(*this); +} + +template class SymbolDocumentation<std::string>; +template class SymbolDocumentation<llvm::StringRef>; + +} // namespace clangd +} // namespace clang Index: clang-tools-extra/clangd/Hover.h =================================================================== --- clang-tools-extra/clangd/Hover.h +++ 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" @@ -68,7 +69,7 @@ std::string Name; llvm::Optional<Range> SymRange; index::SymbolKind Kind = index::SymbolKind::Unknown; - std::string Documentation; + SymbolDocumentationOwned Documentation; /// Source code containing the definition of the symbol. std::string Definition; const char *DefinitionLanguage = "cpp"; Index: clang-tools-extra/clangd/Hover.cpp =================================================================== --- clang-tools-extra/clangd/Hover.cpp +++ clang-tools-extra/clangd/Hover.cpp @@ -332,7 +332,7 @@ LookupRequest Req; Req.IDs.insert(ID); Index->lookup(Req, [&](const Symbol &S) { - Hover.Documentation = std::string(S.Documentation); + Hover.Documentation = S.Documentation.toOwned(); }); } @@ -590,10 +590,11 @@ HI.Name = printName(Ctx, *D); const auto *CommentD = getDeclForComment(D); - HI.Documentation = getDeclComment(Ctx, *CommentD); + HI.Documentation = getDeclDocumentation(Ctx, *CommentD); enhanceFromIndex(HI, *CommentD, Index); if (HI.Documentation.empty()) - HI.Documentation = synthesizeDocumentation(D); + HI.Documentation = + SymbolDocumentationOwned::descriptionOnly(synthesizeDocumentation(D)); HI.Kind = index::getSymbolInfo(D).Kind; @@ -720,7 +721,7 @@ if (const auto *D = QT->getAsTagDecl()) { const auto *CommentD = getDeclForComment(D); - HI.Documentation = getDeclComment(ASTCtx, *CommentD); + HI.Documentation = getDeclDocumentation(ASTCtx, *CommentD); enhanceFromIndex(HI, *CommentD, Index); } } @@ -785,7 +786,8 @@ llvm::raw_string_ostream OS(HI.Definition); A->printPretty(OS, AST.getASTContext().getPrintingPolicy()); } - HI.Documentation = Attr::getDocumentation(A->getKind()).str(); + HI.Documentation = SymbolDocumentationOwned::descriptionOnly( + Attr::getDocumentation(A->getKind()).str()); return HI; } @@ -1086,6 +1088,10 @@ // Put a linebreak after header to increase readability. Output.addRuler(); + + if (!Documentation.Brief.empty()) + parseDocumentation(Documentation.Brief, Output); + // Print Types on their own lines to reduce chances of getting line-wrapped by // editor, as they might be long. if (ReturnType) { @@ -1094,15 +1100,44 @@ // Parameters: // - `bool param1` // - `int param2 = 5` - Output.addParagraph().appendText("â ").appendCode( + auto &P = Output.addParagraph().appendText("â ").appendCode( llvm::to_string(*ReturnType)); - } + if (!Documentation.Returns.empty()) + P.appendText(": ").appendText(Documentation.Returns); + } if (Parameters && !Parameters->empty()) { Output.addParagraph().appendText("Parameters: "); markup::BulletList &L = Output.addBulletList(); - for (const auto &Param : *Parameters) - L.addItem().addParagraph().appendCode(llvm::to_string(Param)); + + llvm::SmallVector<ParameterDocumentationOwned> ParamDocs = + Documentation.Parameters; + + for (const auto &Param : *Parameters) { + auto &Paragraph = L.addItem().addParagraph(); + Paragraph.appendCode(llvm::to_string(Param)); + + if (Param.Name.has_value()) { + auto ParamDoc = std::find_if(ParamDocs.begin(), ParamDocs.end(), + [Param](const auto &ParamDoc) { + return Param.Name == ParamDoc.Name; + }); + if (ParamDoc != ParamDocs.end()) { + Paragraph.appendText(": ").appendText(ParamDoc->Description); + ParamDocs.erase(ParamDoc); + } + } + } + + // We erased all parameters that matched, but some may still be left, + // usually typos. Let's also print them here. + for (const auto &ParamDoc : ParamDocs) { + L.addItem() + .addParagraph() + .appendCode(ParamDoc.Name) + .appendText(": ") + .appendText(ParamDoc.Description); + } } // Don't print Type after Parameters or ReturnType as this will just duplicate @@ -1146,8 +1181,30 @@ Output.addParagraph().appendText(OS.str()); } - if (!Documentation.empty()) - parseDocumentation(Documentation, Output); + if (!Documentation.Description.empty()) + parseDocumentation(Documentation.Description, Output); + + if (!Documentation.Warnings.empty()) { + Output.addRuler(); + Output.addParagraph() + .appendText("Warning") + .appendText(Documentation.Warnings.size() > 1 ? "s" : "") + .appendText(": "); + markup::BulletList &L = Output.addBulletList(); + for (const auto &Warning : Documentation.Warnings) + parseDocumentation(Warning, L.addItem()); + } + + if (!Documentation.Notes.empty()) { + Output.addRuler(); + Output.addParagraph() + .appendText("Note") + .appendText(Documentation.Notes.size() > 1 ? "s" : "") + .appendText(": "); + markup::BulletList &L = Output.addBulletList(); + for (const auto &Note : Documentation.Notes) + parseDocumentation(Note, L.addItem()); + } if (!Definition.empty()) { Output.addRuler(); Index: clang-tools-extra/clangd/CodeCompletionStrings.h =================================================================== --- clang-tools-extra/clangd/CodeCompletionStrings.h +++ clang-tools-extra/clangd/CodeCompletionStrings.h @@ -14,26 +14,28 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H +#include "clang/AST/CommentVisitor.h" #include "clang/Sema/CodeCompleteConsumer.h" +#include "SymbolDocumentation.h" + namespace clang { class ASTContext; namespace clangd { -/// Gets a minimally formatted documentation comment of \p Result, with comment -/// markers stripped. See clang::RawComment::getFormattedText() for the detailed -/// explanation of how the comment text is transformed. -/// Returns empty string when no comment is available. +/// Gets the parsed doxygen documentation of \p Result. +/// Returns an empty SymbolDocumentationOwned when no comment is available. /// If \p CommentsFromHeaders parameter is set, only comments from the main /// file will be returned. It is used to workaround crashes when parsing /// comments in the stale headers, coming from completion preamble. -std::string getDocComment(const ASTContext &Ctx, - const CodeCompletionResult &Result, - bool CommentsFromHeaders); +SymbolDocumentationOwned getDocumentation(const ASTContext &Ctx, + const CodeCompletionResult &Result, + bool CommentsFromHeaders); -/// Similar to getDocComment, but returns the comment for a NamedDecl. -std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &D); +/// Similar to getDocumentation, but returns the comment for a NamedDecl. +SymbolDocumentationOwned getDeclDocumentation(const ASTContext &Ctx, + const NamedDecl &D); /// Formats the signature for an item, as a display string and snippet. /// e.g. for const_reference std::vector<T>::at(size_type) const, this returns: Index: clang-tools-extra/clangd/CodeCompletionStrings.cpp =================================================================== --- clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -58,39 +58,42 @@ } // namespace -std::string getDocComment(const ASTContext &Ctx, - const CodeCompletionResult &Result, - bool CommentsFromHeaders) { +SymbolDocumentationOwned getDocumentation(const ASTContext &Ctx, + const CodeCompletionResult &Result, + bool CommentsFromHeaders) { + // FIXME: CommentsFromHeaders seems to be unused? Is this a bug? + // FIXME: clang's completion also returns documentation for RK_Pattern if they // contain a pattern for ObjC properties. Unfortunately, there is no API to // get this declaration, so we don't show documentation in that case. if (Result.Kind != CodeCompletionResult::RK_Declaration) - return ""; - return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration()) - : ""; + return {}; + return Result.getDeclaration() + ? getDeclDocumentation(Ctx, *Result.getDeclaration()) + : SymbolDocumentationOwned{}; } -std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { +SymbolDocumentationOwned getDeclDocumentation(const ASTContext &Ctx, + const NamedDecl &Decl) { if (isa<NamespaceDecl>(Decl)) { // Namespaces often have too many redecls for any particular redecl comment // to be useful. Moreover, we often confuse file headers or generated // comments with namespace comments. Therefore we choose to just ignore // the comments for namespaces. - return ""; + return {}; } const RawComment *RC = getCompletionComment(Ctx, &Decl); if (!RC) - return ""; + return {}; // Sanity check that the comment does not come from the PCH. We choose to not // write them into PCH, because they are racy and slow to load. assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); - std::string Doc = - RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); - if (!looksLikeDocComment(Doc)) - return ""; - // Clang requires source to be UTF-8, but doesn't enforce this in comments. - if (!llvm::json::isUTF8(Doc)) - Doc = llvm::json::fixUTF8(Doc); + + SymbolDocumentationOwned Doc = parseDoxygenComment(*RC, Ctx, &Decl); + + if (!looksLikeDocComment(Doc.CommentText)) + return {}; + return Doc; } Index: clang-tools-extra/clangd/CodeComplete.cpp =================================================================== --- clang-tools-extra/clangd/CodeComplete.cpp +++ clang-tools-extra/clangd/CodeComplete.cpp @@ -420,11 +420,11 @@ } }; if (C.IndexResult) { - SetDoc(C.IndexResult->Documentation); + SetDoc(C.IndexResult->Documentation.CommentText); } else if (C.SemaResult) { - const auto DocComment = getDocComment(*ASTCtx, *C.SemaResult, - /*CommentsFromHeaders=*/false); - SetDoc(formatDocumentation(*SemaCCS, DocComment)); + const auto DocComment = getDocumentation(*ASTCtx, *C.SemaResult, + /*CommentsFromHeaders=*/false); + SetDoc(formatDocumentation(*SemaCCS, DocComment.CommentText)); } } if (Completion.Deprecated) { @@ -977,8 +977,9 @@ ScoredSignatures.push_back(processOverloadCandidate( Candidate, *CCS, Candidate.getFunction() - ? getDeclComment(S.getASTContext(), *Candidate.getFunction()) - : "")); + ? getDeclDocumentation(S.getASTContext(), + *Candidate.getFunction()) + : SymbolDocumentationOwned{})); } // Sema does not load the docs from the preamble, so we need to fetch extra @@ -993,7 +994,7 @@ } Index->lookup(IndexRequest, [&](const Symbol &S) { if (!S.Documentation.empty()) - FetchedDocs[S.ID] = std::string(S.Documentation); + FetchedDocs[S.ID] = std::string(S.Documentation.CommentText); }); vlog("SigHelp: requested docs for {0} symbols from the index, got {1} " "symbols with non-empty docs in the response", @@ -1102,15 +1103,17 @@ // FIXME(ioeric): consider moving CodeCompletionString logic here to // CompletionString.h. - ScoredSignature processOverloadCandidate(const OverloadCandidate &Candidate, - const CodeCompletionString &CCS, - llvm::StringRef DocComment) const { + ScoredSignature + processOverloadCandidate(const OverloadCandidate &Candidate, + const CodeCompletionString &CCS, + const SymbolDocumentationOwned &DocComment) const { SignatureInformation Signature; SignatureQualitySignals Signal; const char *ReturnType = nullptr; markup::Document OverloadComment; - parseDocumentation(formatDocumentation(CCS, DocComment), OverloadComment); + parseDocumentation(formatDocumentation(CCS, DocComment.CommentText), + OverloadComment); Signature.documentation = renderDoc(OverloadComment, DocumentationFormat); Signal.Kind = Candidate.getKind(); Index: clang-tools-extra/clangd/CMakeLists.txt =================================================================== --- clang-tools-extra/clangd/CMakeLists.txt +++ clang-tools-extra/clangd/CMakeLists.txt @@ -97,6 +97,7 @@ SemanticHighlighting.cpp SemanticSelection.cpp SourceCode.cpp + SymbolDocumentation.cpp QueryDriverDatabase.cpp TidyProvider.cpp TUScheduler.cpp
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits