Index: clangd/AST.h =================================================================== --- clangd/AST.h +++ clangd/AST.h @@ -42,6 +42,11 @@ /// Returns the first enclosing namespace scope starting from \p DC. std::string printNamespaceScope(const DeclContext &DC); +/// Prints unqualified name of the decl for the purpose of displaying it to the +/// user. Anonymous decls return names of the form "(anonymous {kind})", e.g. +/// "(anonymous struct)" or "(anonymous namespace)". +std::string printName(const NamedDecl &ND); + /// Gets the symbol ID for a declaration, if possible. llvm::Optional getSymbolID(const Decl *D); Index: clangd/AST.cpp =================================================================== --- clangd/AST.cpp +++ clangd/AST.cpp @@ -14,6 +14,7 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Index/USRGeneration.h" +#include "llvm/Support/ScopedPrinter.h" using namespace llvm; namespace clang { @@ -64,6 +65,18 @@ return QName; } +std::string printName(const NamedDecl &ND) { + const NamedDecl *NameSource = &ND; + std::string Name = llvm::to_string(NameSource->getDeclName()); + if (!Name.empty()) + return Name; + if (auto NS = llvm::dyn_cast(NameSource)) + return "(anonymous namespace)"; + if (auto Cls = llvm::dyn_cast(NameSource)) + return ("(anonymous " + Cls->getKindName() + ")").str(); + return "(anonymous)"; +} + std::string printNamespaceScope(const DeclContext &DC) { for (const auto *Ctx = &DC; Ctx != nullptr; Ctx = Ctx->getParent()) if (const auto *NS = dyn_cast(Ctx)) Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -64,8 +64,11 @@ Callback>); void onDocumentFormatting(const DocumentFormattingParams &, Callback>); + // The results are serialized 'vector' if + // SupportsHierarchicalDocumentSymbol is true and 'vector' + // otherwise. void onDocumentSymbol(const DocumentSymbolParams &, - Callback>); + Callback); void onCodeAction(const CodeActionParams &, Callback); void onCompletion(const TextDocumentPositionParams &, Callback); @@ -166,6 +169,8 @@ CompletionItemKindBitset SupportedCompletionItemKinds; // Whether the client supports CodeAction response objects. bool SupportsCodeAction = false; + /// From capabilities of textDocument/documentSymbol. + bool SupportsHierarchicalDocumentSymbol = false; // Store of the current versions of the open documents. DraftStore DraftMgr; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -23,6 +23,53 @@ namespace clangd { namespace { +/// The functions constructs a flattened view of the DocumentSymbol hierarchy. +/// Used by the clients that do not support the hierarchical view. +static std::vector +flattenDocumentSymbols(llvm::ArrayRef Symbols, + const URIForFile &FileURI) { + // A helper struct to keep the state for recursive processing, computes + // results on construction. + struct FlattenRec { + FlattenRec(const URIForFile &FileURI, + llvm::ArrayRef Symbols) + : FileURI(FileURI) { + for (auto &S : Symbols) + process(S, /*ParentName=*/""); + } + + std::vector results() && { return std::move(Results); } + + private: + void process(const DocumentSymbol &S, llvm::StringRef ParentName) { + SymbolInformation SI; + SI.containerName = ParentName; + SI.name = S.name; + SI.kind = S.kind; + SI.location.range = S.range; + SI.location.uri = FileURI; + + Results.push_back(std::move(SI)); + std::string FullName = + ParentName.empty() ? S.name : (ParentName.str() + "::" + S.name); + for (auto &C : S.children) + process(C, /*ParentName=*/FullName); + } + + std::vector Results; + const URIForFile &FileURI; + }; + return FlattenRec(FileURI, Symbols).results(); +} + +static void adjustSymbolKinds(llvm::MutableArrayRef Syms, + SymbolKindBitset Kinds) { + for (auto &S : Syms) { + S.kind = adjustKindToCapability(S.kind, Kinds); + adjustSymbolKinds(S.children, Kinds); + } +} + /// \brief Supports a test URI scheme with relaxed constraints for lit tests. /// The path in a test URI will be combined with a platform-specific fake /// directory to form an absolute path. For example, test:///a.cpp is resolved @@ -283,6 +330,8 @@ if (Params.capabilities.CompletionItemKinds) SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds; SupportsCodeAction = Params.capabilities.CodeActionStructure; + SupportsHierarchicalDocumentSymbol = + Params.capabilities.HierarchicalDocumentSymbol; Reply(json::Object{ {{"capabilities", @@ -504,19 +553,21 @@ Reply(ReplacementsOrError.takeError()); } -void ClangdLSPServer::onDocumentSymbol( - const DocumentSymbolParams &Params, - Callback> Reply) { +void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params, + Callback Reply) { + URIForFile FileURI = Params.textDocument.uri; Server->documentSymbols( Params.textDocument.uri.file(), Bind( - [this](decltype(Reply) Reply, - Expected> Items) { + [this, FileURI](decltype(Reply) Reply, + Expected> Items) { if (!Items) return Reply(Items.takeError()); - for (auto &Sym : *Items) - Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); - Reply(std::move(*Items)); + adjustSymbolKinds(*Items, SupportedSymbolKinds); + if (SupportsHierarchicalDocumentSymbol) + return Reply(std::move(*Items)); + else + return Reply(flattenDocumentSymbols(*Items, FileURI)); }, std::move(Reply))); } Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -170,7 +170,7 @@ /// Retrieve the symbols within the specified file. void documentSymbols(StringRef File, - Callback> CB); + Callback> CB); /// Retrieve locations for symbol references. void findReferences(PathRef File, Position Pos, Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -486,10 +486,10 @@ WorkspaceRoot.getValueOr(""))); } -void ClangdServer::documentSymbols( - StringRef File, Callback> CB) { - auto Action = [](Callback> CB, - Expected InpAST) { +void ClangdServer::documentSymbols(StringRef File, + Callback> CB) { + auto Action = [](Callback> CB, + llvm::Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); CB(clangd::getDocumentSymbols(InpAST->AST)); Index: clangd/FindSymbols.h =================================================================== --- clangd/FindSymbols.h +++ clangd/FindSymbols.h @@ -36,8 +36,7 @@ /// Retrieves the symbols contained in the "main file" section of an AST in the /// same order that they appear. -llvm::Expected> -getDocumentSymbols(ParsedAST &AST); +llvm::Expected> getDocumentSymbols(ParsedAST &AST); } // namespace clangd } // namespace clang Index: clangd/FindSymbols.cpp =================================================================== --- clangd/FindSymbols.cpp +++ clangd/FindSymbols.cpp @@ -15,11 +15,13 @@ #include "Quality.h" #include "SourceCode.h" #include "index/Index.h" +#include "clang/AST/DeclTemplate.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" #define DEBUG_TYPE "FindSymbols" @@ -176,104 +178,134 @@ } namespace { -/// Finds document symbols in the main file of the AST. -class DocumentSymbolsConsumer : public index::IndexDataConsumer { - ASTContext &AST; - std::vector Symbols; - // We are always list document for the same file, so cache the value. - Optional MainFileUri; +DocumentSymbol declToSym(ASTContext &Ctx, const NamedDecl &ND) { + SourceLocation NameLoc = findNameLoc(&ND); -public: - DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {} - std::vector takeSymbols() { return std::move(Symbols); } + auto &SM = Ctx.getSourceManager(); + Position NameBegin = sourceLocToPosition(SM, NameLoc); + Position NameEnd = sourceLocToPosition( + SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts())); - void initialize(ASTContext &Ctx) override { - // Compute the absolute path of the main file which we will use for all - // results. - const SourceManager &SM = AST.getSourceManager(); - const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID()); - if (!F) - return; - auto FilePath = getRealPath(F, SM); - if (FilePath) - MainFileUri = URIForFile(*FilePath); - } + index::SymbolInfo SymInfo = index::getSymbolInfo(&ND); + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); - bool shouldIncludeSymbol(const NamedDecl *ND) { - if (!ND || ND->isImplicit()) - return false; - // Skip anonymous declarations, e.g (anonymous enum/class/struct). - if (ND->getDeclName().isEmpty()) - return false; - return true; + DocumentSymbol SI; + SI.name = printName(ND); + SI.kind = SK; + SI.deprecated = ND.isDeprecated(); + SI.range = Range{sourceLocToPosition(SM, ND.getBeginLoc()), + sourceLocToPosition(SM, ND.getEndLoc())}; + SI.selectionRange = Range{NameBegin, NameEnd}; + if (!SI.range.containsSubrange(SI.selectionRange)) { + // 'selectionRange' must be contained in 'range', so in cases where clang + // reports unrelated ranges we need to reconcile somehow. + SI.range = SI.selectionRange; } + return SI; +} - bool - handleDeclOccurence(const Decl *, index::SymbolRoleSet Roles, - ArrayRef Relations, - SourceLocation Loc, - index::IndexDataConsumer::ASTNodeInfo ASTNode) override { - assert(ASTNode.OrigD); - // No point in continuing the index consumer if we could not get the - // absolute path of the main file. - if (!MainFileUri) - return false; - // We only want declarations and definitions, i.e. no references. - if (!(Roles & static_cast(index::SymbolRole::Declaration) || - Roles & static_cast(index::SymbolRole::Definition))) - return true; - SourceLocation NameLoc = findNameLoc(ASTNode.OrigD); - const SourceManager &SourceMgr = AST.getSourceManager(); - // We should be only be looking at "local" decls in the main file. - if (!SourceMgr.isWrittenInMainFile(NameLoc)) { - // Even thought we are visiting only local (non-preamble) decls, - // we can get here when in the presence of "extern" decls. - return true; +std::vector collectDocSymbols(ParsedAST &AST) { + struct CollectSymbols { + CollectSymbols(ParsedAST &AST, std::vector &Results) + : Ctx(AST.getASTContext()) { + for (auto &TopLevel : AST.getLocalTopLevelDecls()) + traverseDecl(TopLevel, Results); } - const NamedDecl *ND = dyn_cast(ASTNode.OrigD); - if (!shouldIncludeSymbol(ND)) - return true; - SourceLocation EndLoc = - Lexer::getLocForEndOfToken(NameLoc, 0, SourceMgr, AST.getLangOpts()); - Position Begin = sourceLocToPosition(SourceMgr, NameLoc); - Position End = sourceLocToPosition(SourceMgr, EndLoc); - Range R = {Begin, End}; - Location L; - L.uri = *MainFileUri; - L.range = R; + private: + enum class VisitKind { + No, + OnlyDecl, + DeclAndChildren + }; - std::string QName = printQualifiedName(*ND); - StringRef Scope, Name; - std::tie(Scope, Name) = splitQualifiedName(QName); - Scope.consume_back("::"); + void traverseDecl(Decl *D, std::vector &Results) { + if (auto *Templ = llvm::dyn_cast(D)) + D = Templ->getTemplatedDecl(); + auto *ND = llvm::dyn_cast(D); + if (!ND) + return; + VisitKind Visit = shouldVisit(D); + if (Visit == VisitKind::No) + return; + if (auto *Enum = llvm::dyn_cast(ND)) { + // Do not add anonymous enums, promote their children to the parent + // level instead. + if (!Enum->getIdentifier()) { + assert(Visit == VisitKind::DeclAndChildren); + return traverseChildren(D, Results); + } + } + DocumentSymbol Sym = declToSym(Ctx, *ND); + if (Visit == VisitKind::DeclAndChildren) + traverseChildren(D, Sym.children); + Results.push_back(std::move(Sym)); + } - index::SymbolInfo SymInfo = index::getSymbolInfo(ND); - SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + void traverseChildren(Decl *D, std::vector &Results) { + auto *Scope = llvm::dyn_cast(D); + if (!Scope) + return; + for (auto *C : Scope->decls()) + traverseDecl(C, Results); + } - SymbolInformation SI; - SI.name = Name; - SI.kind = SK; - SI.location = L; - SI.containerName = Scope; - Symbols.push_back(std::move(SI)); - return true; - } -}; -} // namespace + VisitKind shouldVisit(Decl* D) { + if (D->isImplicit()) + return VisitKind::No; -Expected> getDocumentSymbols(ParsedAST &AST) { - DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext()); + auto &SM = Ctx.getSourceManager(); + if (!SM.isWrittenInMainFile(SM.getFileLoc(D->getLocation()))) + return VisitKind::No; + + if (auto Func = llvm::dyn_cast(D)) { + // Some functions are implicit template instantiations, those should be + // ignored. + if (auto *Info = Func->getTemplateSpecializationInfo()) { + if (!Info->isExplicitInstantiationOrSpecialization()) + return VisitKind::No; + } + // Only visit the function itself, do not visit the children (i.e. + // function parameters, etc.) + return VisitKind::OnlyDecl; + } + // Handle template instantiations. We have three cases to consider: + // - explicit instantiations, e.g. 'template class std::vector;' + // Visit the decl itself (it's present in the code), but not the + // children. + // - implicit instantiations, i.e. not written by the user. + // Do not visit at all, they are not present in the code. + // - explicit specialization, e.g. 'template <> class vector {};' + // Visit both the decl and its children, both are written in the code. + if (auto *TemplSpec = llvm::dyn_cast(D)){ + if (TemplSpec->isExplicitInstantiationOrSpecialization()) + return TemplSpec->isExplicitSpecialization() + ? VisitKind::DeclAndChildren + : VisitKind::OnlyDecl; + return VisitKind::No; + } + if (auto *TemplSpec = llvm::dyn_cast(D)) { + if (TemplSpec->isExplicitInstantiationOrSpecialization()) + return TemplSpec->isExplicitSpecialization() + ? VisitKind::DeclAndChildren + : VisitKind::OnlyDecl; + return VisitKind::No; + } + // For all other cases, visit both the children and the decl. + return VisitKind::DeclAndChildren; + } - index::IndexingOptions IndexOpts; - IndexOpts.SystemSymbolFilter = - index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; - IndexOpts.IndexFunctionLocals = false; - indexTopLevelDecls(AST.getASTContext(), AST.getPreprocessor(), - AST.getLocalTopLevelDecls(), DocumentSymbolsCons, - IndexOpts); + ASTContext &Ctx; + }; + + std::vector Results; + CollectSymbols(AST, Results); + return Results; +} +} // namespace - return DocumentSymbolsCons.takeSymbols(); +llvm::Expected> getDocumentSymbols(ParsedAST &AST) { + return collectDocSymbols(AST); } } // namespace clangd Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -40,6 +40,7 @@ InvalidRequest = -32600, MethodNotFound = -32601, InvalidParams = -32602, + InternalError = -32603, ServerNotInitialized = -32002, @@ -150,6 +151,9 @@ } bool contains(Position Pos) const { return start <= Pos && Pos < end; } + bool containsSubrange(Range Rng) const { + return start <= Rng.start && Rng.end <= end; + } }; bool fromJSON(const llvm::json::Value &, Range &); llvm::json::Value toJSON(const Range &); @@ -336,6 +340,9 @@ /// textDocument.completion.completionItem.snippetSupport bool CompletionSnippets = false; + /// Client supports hierarchical document symbols. + bool HierarchicalDocumentSymbol = false; + /// The supported set of CompletionItemKinds for textDocument/completion. /// textDocument.completion.completionItemKind.valueSet llvm::Optional CompletionItemKinds; @@ -661,6 +668,39 @@ }; llvm::json::Value toJSON(const CodeAction &); +/// Represents programming constructs like variables, classes, interfaces etc. +/// that appear in a document. Document symbols can be hierarchical and they +/// have two ranges: one that encloses its definition and one that points to its +/// most interesting range, e.g. the range of an identifier. +struct DocumentSymbol { + /// The name of this symbol. + std::string name; + + /// More detail for this symbol, e.g the signature of a function. + std::string detail; + + /// The kind of this symbol. + SymbolKind kind; + + /// Indicates if this symbol is deprecated. + bool deprecated; + + /// The range enclosing this symbol not including leading/trailing whitespace + /// but everything else like comments. This information is typically used to + /// determine if the clients cursor is inside the symbol to reveal in the + /// symbol in the UI. + Range range; + + /// The range that should be selected and revealed when this symbol is being + /// picked, e.g the name of a function. Must be contained by the `range`. + Range selectionRange; + + /// Children of this symbol, e.g. properties of a class. + std::vector children; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S); +llvm::json::Value toJSON(const DocumentSymbol &S); + /// Represents information about programming constructs like variables, classes, /// interfaces etc. struct SymbolInformation { Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -18,6 +18,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" @@ -230,6 +231,11 @@ if (CodeAction->getObject("codeActionLiteralSupport")) R.CodeActionStructure = true; } + if (auto *DocumentSymbol = TextDocument->getObject("documentSymbol")) { + if (auto HierarchicalSupport = + DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport")) + R.HierarchicalDocumentSymbol = *HierarchicalSupport; + } } if (auto *Workspace = O->getObject("workspace")) { if (auto *Symbol = Workspace->getObject("symbol")) { @@ -458,6 +464,25 @@ return std::move(CodeAction); } +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S) { + return O << S.name << " - " << toJSON(S); +} + +llvm::json::Value toJSON(const DocumentSymbol &S) { + json::Object Result{{"name", S.name}, + {"kind", static_cast(S.kind)}, + {"range", S.range}, + {"selectionRange", S.selectionRange}}; + + if (!S.detail.empty()) + Result["detail"] = S.detail; + if (!S.children.empty()) + Result["children"] = S.children; + if (S.deprecated) + Result["deprecated"] = true; + return Result; +} + json::Value toJSON(const WorkspaceEdit &WE) { if (!WE.changes) return json::Object{}; Index: clangd/clients/clangd-vscode/package.json =================================================================== --- clangd/clients/clangd-vscode/package.json +++ clangd/clients/clangd-vscode/package.json @@ -6,7 +6,7 @@ "publisher": "llvm-vs-code-extensions", "homepage": "https://clang.llvm.org/extra/clangd.html", "engines": { - "vscode": "^1.18.0" + "vscode": "^1.27.0" }, "categories": [ "Programming Languages", @@ -32,8 +32,8 @@ "test": "node ./node_modules/vscode/bin/test" }, "dependencies": { - "vscode-languageclient": "^4.0.0", - "vscode-languageserver": "^4.0.0" + "vscode-languageclient": "^5.1.0", + "vscode-languageserver": "^5.1.0" }, "devDependencies": { "typescript": "^2.0.3", Index: unittests/clangd/FindSymbolsTests.cpp =================================================================== --- unittests/clangd/FindSymbolsTests.cpp +++ unittests/clangd/FindSymbolsTests.cpp @@ -23,6 +23,7 @@ using ::testing::AnyOf; using ::testing::ElementsAre; using ::testing::ElementsAreArray; +using ::testing::Field; using ::testing::IsEmpty; using ::testing::UnorderedElementsAre; @@ -37,9 +38,22 @@ return arg.name == Name; return (arg.containerName + "::" + arg.name) == Name; } +MATCHER_P(WithName, N, "") { return arg.name == N; } MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; } +// GMock helpers for matching DocumentSymbol. +MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; } +testing::Matcher +Children(testing::Matcher> ChildrenM) { + return Field(&DocumentSymbol::children, ChildrenM); +} +template +testing::Matcher ChildrenAre(ChildMatchers... ChildrenM) { + return Children(ElementsAre(ChildrenM...)); +} +testing::Matcher NoChildren() { return Children(IsEmpty()); } + ClangdServer::Options optsForTests() { auto ServerOpts = ClangdServer::optsForTest(); ServerOpts.WorkspaceRoot = testRoot(); @@ -301,7 +315,7 @@ IgnoreDiagnostics DiagConsumer; ClangdServer Server; - std::vector getSymbols(PathRef File) { + std::vector getSymbols(PathRef File) { EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; auto SymbolInfos = runDocumentSymbols(Server, File); EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error"; @@ -364,31 +378,49 @@ )"); addFile(FilePath, Main.code()); - EXPECT_THAT(getSymbols(FilePath), - ElementsAreArray( - {AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)), - AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)), - AllOf(QName("Foo::f"), WithKind(SymbolKind::Method)), - AllOf(QName("f1"), WithKind(SymbolKind::Function)), - AllOf(QName("Foo::operator="), WithKind(SymbolKind::Method)), - AllOf(QName("Foo::~Foo"), WithKind(SymbolKind::Method)), - AllOf(QName("Foo::Nested"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo::Nested::f"), WithKind(SymbolKind::Method)), - AllOf(QName("Friend"), WithKind(SymbolKind::Class)), - AllOf(QName("f1"), WithKind(SymbolKind::Function)), - AllOf(QName("f2"), WithKind(SymbolKind::Function)), - AllOf(QName("KInt"), WithKind(SymbolKind::Variable)), - AllOf(QName("kStr"), WithKind(SymbolKind::Variable)), - AllOf(QName("f1"), WithKind(SymbolKind::Function)), - AllOf(QName("foo"), WithKind(SymbolKind::Namespace)), - AllOf(QName("foo::int32"), WithKind(SymbolKind::Class)), - AllOf(QName("foo::int32_t"), WithKind(SymbolKind::Class)), - AllOf(QName("foo::v1"), WithKind(SymbolKind::Variable)), - AllOf(QName("foo::bar"), WithKind(SymbolKind::Namespace)), - AllOf(QName("foo::bar::v2"), WithKind(SymbolKind::Variable)), - AllOf(QName("foo::baz"), WithKind(SymbolKind::Namespace))})); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAreArray( + {AllOf(WithName("Foo"), WithKind(SymbolKind::Class), NoChildren()), + AllOf(WithName("Foo"), WithKind(SymbolKind::Class), + ChildrenAre( + AllOf(WithName("Foo"), WithKind(SymbolKind::Method), + NoChildren()), + AllOf(WithName("Foo"), WithKind(SymbolKind::Method), + NoChildren()), + AllOf(WithName("f"), WithKind(SymbolKind::Method), + NoChildren()), + AllOf(WithName("operator="), WithKind(SymbolKind::Method), + NoChildren()), + AllOf(WithName("~Foo"), WithKind(SymbolKind::Method), + NoChildren()), + AllOf(WithName("Nested"), WithKind(SymbolKind::Class), + ChildrenAre(AllOf(WithName("f"), + WithKind(SymbolKind::Method), + NoChildren()))))), + AllOf(WithName("Friend"), WithKind(SymbolKind::Class), NoChildren()), + AllOf(WithName("f1"), WithKind(SymbolKind::Function), NoChildren()), + AllOf(WithName("f2"), WithKind(SymbolKind::Function), NoChildren()), + AllOf(WithName("KInt"), WithKind(SymbolKind::Variable), + NoChildren()), + AllOf(WithName("kStr"), WithKind(SymbolKind::Variable), + NoChildren()), + AllOf(WithName("f1"), WithKind(SymbolKind::Function), NoChildren()), + AllOf(WithName("foo"), WithKind(SymbolKind::Namespace), + ChildrenAre( + AllOf(WithName("int32"), WithKind(SymbolKind::Class), + NoChildren()), + AllOf(WithName("int32_t"), WithKind(SymbolKind::Class), + NoChildren()), + AllOf(WithName("v1"), WithKind(SymbolKind::Variable), + NoChildren()), + AllOf(WithName("bar"), WithKind(SymbolKind::Namespace), + ChildrenAre(AllOf(WithName("v2"), + WithKind(SymbolKind::Variable), + NoChildren()))), + AllOf(WithName("baz"), WithKind(SymbolKind::Namespace), + NoChildren()), + AllOf(WithName("v2"), WithKind(SymbolKind::Variable))))})); } TEST_F(DocumentSymbolsTest, DeclarationDefinition) { @@ -403,11 +435,12 @@ addFile(FilePath, Main.code()); EXPECT_THAT(getSymbols(FilePath), - ElementsAre(AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo::f"), WithKind(SymbolKind::Method), - SymRange(Main.range("decl"))), - AllOf(QName("Foo::f"), WithKind(SymbolKind::Method), - SymRange(Main.range("def"))))); + ElementsAre(AllOf(WithName("Foo"), WithKind(SymbolKind::Class), + ChildrenAre(AllOf( + WithName("f"), WithKind(SymbolKind::Method), + SymNameRange(Main.range("decl"))))), + AllOf(WithName("f"), WithKind(SymbolKind::Method), + SymNameRange(Main.range("def"))))); } TEST_F(DocumentSymbolsTest, ExternSymbol) { @@ -430,7 +463,7 @@ struct LocalClass {}; int local_var; })cpp"); - EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test"))); + EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test"))); } TEST_F(DocumentSymbolsTest, Unnamed) { @@ -443,9 +476,12 @@ )cpp"); EXPECT_THAT( getSymbols(FilePath), - ElementsAre(AllOf(QName("UnnamedStruct"), WithKind(SymbolKind::Variable)), - AllOf(QName("(anonymous struct)::InUnnamed"), - WithKind(SymbolKind::Field)))); + ElementsAre( + AllOf(WithName("(anonymous struct)"), WithKind(SymbolKind::Struct), + ChildrenAre(AllOf(WithName("InUnnamed"), + WithKind(SymbolKind::Field), NoChildren()))), + AllOf(WithName("UnnamedStruct"), WithKind(SymbolKind::Variable), + NoChildren()))); } TEST_F(DocumentSymbolsTest, InHeaderFile) { @@ -462,7 +498,7 @@ addFile("foo.cpp", R"cpp( #include "foo.h" )cpp"); - EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test"))); + EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test"))); } TEST_F(DocumentSymbolsTest, Template) { @@ -471,14 +507,21 @@ // Primary templates and specializations are included but instantiations // are not. template struct Tmpl {T x = 0;}; - template <> struct Tmpl {}; + template <> struct Tmpl { + int y = 0; + }; extern template struct Tmpl; template struct Tmpl; )"); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct)), - AllOf(QName("Tmpl::x"), WithKind(SymbolKind::Field)), - AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct)))); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAre( + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), + ChildrenAre(AllOf(WithName("x"), WithKind(SymbolKind::Field)))), + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), + ChildrenAre(WithName("y"))), + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), NoChildren()), + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), NoChildren()))); } TEST_F(DocumentSymbolsTest, Namespaces) { @@ -506,12 +549,20 @@ } } )cpp"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAreArray({QName("ans1"), QName("ans1::ai1"), QName("ans1::ans2"), - QName("ans1::ans2::ai2"), QName("test"), QName("na"), - QName("na::nb"), QName("na::Foo"), QName("na"), - QName("na::nb"), QName("na::Bar")})); + EXPECT_THAT(getSymbols(FilePath), + ElementsAreArray>( + {AllOf(WithName("ans1"), + ChildrenAre(AllOf(WithName("ai1"), NoChildren()), + AllOf(WithName("ans2"), + ChildrenAre(WithName("ai2"))))), + AllOf(WithName("(anonymous namespace)"), + ChildrenAre(WithName("test"))), + AllOf(WithName("na"), + ChildrenAre(AllOf(WithName("nb"), + ChildrenAre(WithName("Foo"))))), + AllOf(WithName("na"), + ChildrenAre(AllOf(WithName("nb"), + ChildrenAre(WithName("Bar")))))})); } TEST_F(DocumentSymbolsTest, Enums) { @@ -532,10 +583,12 @@ }; } )"); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(QName("Red"), QName("Color"), QName("Green"), - QName("Color2"), QName("Color2::Yellow"), QName("ns"), - QName("ns::Black"))); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAre(WithName("Red"), + AllOf(WithName("Color"), ChildrenAre(WithName("Green"))), + AllOf(WithName("Color2"), ChildrenAre(WithName("Yellow"))), + AllOf(WithName("ns"), ChildrenAre(WithName("Black"))))); } TEST_F(DocumentSymbolsTest, FromMacro) { @@ -554,8 +607,24 @@ addFile(FilePath, Main.code()); EXPECT_THAT( getSymbols(FilePath), - ElementsAre(AllOf(QName("abc_Test"), SymRange(Main.range("expansion"))), - AllOf(QName("Test"), SymRange(Main.range("spelling"))))); + ElementsAre( + AllOf(WithName("abc_Test"), SymNameRange(Main.range("expansion"))), + AllOf(WithName("Test"), SymNameRange(Main.range("spelling"))))); +} + +TEST_F(DocumentSymbolsTest, FuncTemplates) { + std::string FilePath = testPath("foo.cpp"); + Annotations Source(R"cpp( + template + T foo() {} + + auto x = foo(); + auto y = foo() + )cpp"); + addFile(FilePath, Source.code()); + // Make sure we only see the template declaration, not instantiations. + EXPECT_THAT(getSymbols(FilePath), + ElementsAre(WithName("foo"), WithName("x"), WithName("y"))); } } // namespace clangd Index: unittests/clangd/SyncAPI.h =================================================================== --- unittests/clangd/SyncAPI.h +++ unittests/clangd/SyncAPI.h @@ -47,7 +47,7 @@ llvm::Expected> runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit); -llvm::Expected> +llvm::Expected> runDocumentSymbols(ClangdServer &Server, PathRef File); SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query); Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -119,9 +119,9 @@ return std::move(*Result); } -Expected> +llvm::Expected> runDocumentSymbols(ClangdServer &Server, PathRef File) { - Optional>> Result; + llvm::Optional>> Result; Server.documentSymbols(File, capture(Result)); return std::move(*Result); }