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 ASTContext &Ctx, 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 @@ -11,9 +11,12 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclTemplate.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Index/USRGeneration.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ScopedPrinter.h" using namespace llvm; namespace clang { @@ -61,6 +64,46 @@ return QName; } +static const TemplateArgumentList * +getTemplateSpecializationArgs(const NamedDecl &ND) { + if (auto *Func = llvm::dyn_cast(&ND)) + return Func->getTemplateSpecializationArgs(); + if (auto *Cls = llvm::dyn_cast(&ND)) + return &Cls->getTemplateInstantiationArgs(); + if (auto *Var = llvm::dyn_cast(&ND)) + return &Var->getTemplateInstantiationArgs(); + return nullptr; +} + +std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { + std::string Name; + llvm::raw_string_ostream Out(Name); + PrintingPolicy PP(Ctx.getLangOpts()); + // Handle 'using namespace'. They all have the same name - . + if (auto *UD = llvm::dyn_cast(&ND)) { + Out << "using namespace "; + if (auto *Qual = UD->getQualifier()) + Qual->print(Out, PP); + UD->getNominatedNamespaceAsWritten()->printName(Out); + return Out.str(); + } + ND.getDeclName().print(Out, PP); + if (!Out.str().empty()) { + // FIXME(ibiryukov): do not show args not explicitly written by the user. + if (auto *ArgList = getTemplateSpecializationArgs(ND)) + printTemplateArgumentList(Out, ArgList->asArray(), PP); + return Out.str(); + } + // The name was empty, so present an anonymous entity. + if (auto *NS = llvm::dyn_cast(&ND)) + return "(anonymous namespace)"; + if (auto *Cls = llvm::dyn_cast(&ND)) + return ("(anonymous " + Cls->getKindName() + ")").str(); + if (auto *En = llvm::dyn_cast(&ND)) + return "(anonymous enum)"; + 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 @@ -66,8 +66,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); @@ -128,6 +131,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,14 @@ namespace clangd { namespace { +void adjustSymbolKinds(llvm::MutableArrayRef Syms, + SymbolKindBitset Kinds) { + for (auto &S : Syms) { + S.kind = adjustKindToCapability(S.kind, Kinds); + adjustSymbolKinds(S.children, Kinds); + } +} + SymbolKindBitset defaultSymbolKinds() { SymbolKindBitset Defaults; for (size_t I = SymbolKindMin; I <= static_cast(SymbolKind::Array); @@ -284,6 +292,8 @@ if (Params.capabilities.CompletionItemKinds) SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds; SupportsCodeAction = Params.capabilities.CodeActionStructure; + SupportsHierarchicalDocumentSymbol = + Params.capabilities.HierarchicalDocumentSymbol; Reply(json::Object{ {{"capabilities", @@ -501,19 +511,48 @@ Reply(ReplacementsOrError.takeError()); } -void ClangdLSPServer::onDocumentSymbol( - const DocumentSymbolParams &Params, - Callback> Reply) { +/// The functions constructs a flattened view of the DocumentSymbol hierarchy. +/// Used by the clients that do not support the hierarchical view. +static std::vector +flattenSymbolHierarchy(llvm::ArrayRef Symbols, + const URIForFile &FileURI) { + + std::vector Results; + std::function Process = + [&](const DocumentSymbol &S, Optional ParentName) { + SymbolInformation SI; + SI.containerName = ParentName ? "" : *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 ? S.name : (ParentName->str() + "::" + S.name); + for (auto &C : S.children) + Process(C, /*ParentName=*/FullName); + }; + for (auto &S : Symbols) + Process(S, /*ParentName=*/""); + return Results; +} + +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(flattenSymbolHierarchy(*Items, FileURI)); }, std::move(Reply))); } Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -167,7 +167,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 @@ -470,10 +470,10 @@ std::move(CB))); } -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" @@ -178,104 +180,146 @@ } 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; +llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND) { + auto &SM = Ctx.getSourceManager(); -public: - DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {} - std::vector takeSymbols() { return std::move(Symbols); } + SourceLocation NameLoc = findNameLoc(&ND); + // getFileLoc is a good choice for us, but we also need to make sure + // sourceLocToPosition won't switch files, so we call getSpellingLoc on top of + // that to make sure it does not switch files. + // FIXME: sourceLocToPosition should not switch files! + SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc())); + SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc())); + if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid()) + return llvm::None; - 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); + if (!SM.isWrittenInMainFile(NameLoc) || !SM.isWrittenInMainFile(BeginLoc) || + !SM.isWrittenInMainFile(EndLoc)) + return llvm::None; + + Position NameBegin = sourceLocToPosition(SM, NameLoc); + Position NameEnd = sourceLocToPosition( + SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts())); + + index::SymbolInfo SymInfo = index::getSymbolInfo(&ND); + // FIXME: this is not classifying constructors, destructors and operators + // correctly (they're all "methods"). + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + + DocumentSymbol SI; + SI.name = printName(Ctx, ND); + SI.kind = SK; + SI.deprecated = ND.isDeprecated(); + SI.range = + Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)}; + SI.selectionRange = Range{NameBegin, NameEnd}; + if (!SI.range.contains(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 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; +/// A helper class to build an outline for the parse AST. It traverse the AST +/// directly instead of using RecursiveASTVisitor (RAV) for three main reasons: +/// - there is no way to keep RAV from traversing subtrees we're not +/// interested in. E.g. not traversing function locals or implicit template +/// instantiations. +/// - it's easier to combine results of recursive passes, e.g. +/// - visiting decls is actually simple, so we don't hit the complicated +/// cases that RAV mostly helps with (types and expressions, etc.) +class DocumentOutline { +public: + DocumentOutline(ParsedAST &AST) : AST(AST) {} + + /// Builds the document outline for the generated AST. + std::vector build() { + std::vector Results; + for (auto &TopLevel : AST.getLocalTopLevelDecls()) + traverseDecl(TopLevel, Results); + return Results; } - 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; - } - const NamedDecl *ND = dyn_cast(ASTNode.OrigD); - if (!shouldIncludeSymbol(ND)) - return true; +private: + enum class VisitKind { No, OnlyDecl, DeclAndChildren }; - 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; + 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(ND); + if (Visit == VisitKind::No) + return; + llvm::Optional Sym = declToSym(AST.getASTContext(), *ND); + if (!Sym) + return; + if (Visit == VisitKind::DeclAndChildren) + traverseChildren(D, Sym->children); + Results.push_back(std::move(*Sym)); + } - std::string QName = printQualifiedName(*ND); - StringRef Scope, Name; - std::tie(Scope, Name) = splitQualifiedName(QName); - Scope.consume_back("::"); + void traverseChildren(Decl *D, std::vector &Results) { + auto *Scope = llvm::dyn_cast(D); + if (!Scope) + return; + for (auto *C : Scope->decls()) + traverseDecl(C, Results); + } - index::SymbolInfo SymInfo = index::getSymbolInfo(ND); - SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + VisitKind shouldVisit(NamedDecl *D) { + if (D->isImplicit()) + return VisitKind::No; - SymbolInformation SI; - SI.name = Name; - SI.kind = SK; - SI.location = L; - SI.containerName = Scope; - Symbols.push_back(std::move(SI)); - return true; + 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; } -}; -} // namespace -Expected> getDocumentSymbols(ParsedAST &AST) { - DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext()); + ParsedAST &AST; +}; - index::IndexingOptions IndexOpts; - IndexOpts.SystemSymbolFilter = - index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; - IndexOpts.IndexFunctionLocals = false; - indexTopLevelDecls(AST.getASTContext(), AST.getPreprocessor(), - AST.getLocalTopLevelDecls(), DocumentSymbolsCons, - IndexOpts); +std::vector collectDocSymbols(ParsedAST &AST) { + return DocumentOutline(AST).build(); +} +} // namespace - return DocumentSymbolsCons.takeSymbols(); +llvm::Expected> getDocumentSymbols(ParsedAST &AST) { + return collectDocSymbols(AST); } } // namespace clangd Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -150,6 +150,9 @@ } bool contains(Position Pos) const { return start <= Pos && Pos < end; } + bool contains(Range Rng) const { + return start <= Rng.start && Rng.end <= end; + } }; bool fromJSON(const llvm::json::Value &, Range &); llvm::json::Value toJSON(const Range &); @@ -331,6 +334,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; @@ -655,6 +661,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" @@ -222,6 +223,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")) { @@ -449,6 +455,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 @@ -14,6 +14,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using namespace llvm; + namespace clang { namespace clangd { @@ -23,6 +25,7 @@ using ::testing::AnyOf; using ::testing::ElementsAre; using ::testing::ElementsAreArray; +using ::testing::Field; using ::testing::IsEmpty; using ::testing::UnorderedElementsAre; @@ -37,9 +40,17 @@ 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; } +template +testing::Matcher Children(ChildMatchers... ChildrenM) { + return Field(&DocumentSymbol::children, ElementsAre(ChildrenM...)); +} + ClangdServer::Options optsForTests() { auto ServerOpts = ClangdServer::optsForTest(); ServerOpts.WorkspaceRoot = testRoot(); @@ -300,7 +311,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"; @@ -363,31 +374,46 @@ )"); 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), Children()), + AllOf(WithName("Foo"), WithKind(SymbolKind::Class), + Children(AllOf(WithName("Foo"), WithKind(SymbolKind::Method), + Children()), + AllOf(WithName("Foo"), WithKind(SymbolKind::Method), + Children()), + AllOf(WithName("f"), WithKind(SymbolKind::Method), + Children()), + AllOf(WithName("operator="), + WithKind(SymbolKind::Method), Children()), + AllOf(WithName("~Foo"), WithKind(SymbolKind::Method), + Children()), + AllOf(WithName("Nested"), WithKind(SymbolKind::Class), + Children(AllOf(WithName("f"), + WithKind(SymbolKind::Method), + Children()))))), + AllOf(WithName("Friend"), WithKind(SymbolKind::Class), Children()), + AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), + AllOf(WithName("f2"), WithKind(SymbolKind::Function), Children()), + AllOf(WithName("KInt"), WithKind(SymbolKind::Variable), Children()), + AllOf(WithName("kStr"), WithKind(SymbolKind::Variable), Children()), + AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), + AllOf(WithName("foo"), WithKind(SymbolKind::Namespace), + Children( + AllOf(WithName("int32"), WithKind(SymbolKind::Class), + Children()), + AllOf(WithName("int32_t"), WithKind(SymbolKind::Class), + Children()), + AllOf(WithName("v1"), WithKind(SymbolKind::Variable), + Children()), + AllOf(WithName("bar"), WithKind(SymbolKind::Namespace), + Children(AllOf(WithName("v2"), + WithKind(SymbolKind::Variable), + Children()))), + AllOf(WithName("baz"), WithKind(SymbolKind::Namespace), + Children()), + AllOf(WithName("v2"), WithKind(SymbolKind::Variable))))})); } TEST_F(DocumentSymbolsTest, DeclarationDefinition) { @@ -402,11 +428,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), + Children(AllOf( + WithName("f"), WithKind(SymbolKind::Method), + SymNameRange(Main.range("decl"))))), + AllOf(WithName("f"), WithKind(SymbolKind::Method), + SymNameRange(Main.range("def"))))); } TEST_F(DocumentSymbolsTest, ExternSymbol) { @@ -429,7 +456,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) { @@ -442,9 +469,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), + Children(AllOf(WithName("InUnnamed"), + WithKind(SymbolKind::Field), Children()))), + AllOf(WithName("UnnamedStruct"), WithKind(SymbolKind::Variable), + Children()))); } TEST_F(DocumentSymbolsTest, InHeaderFile) { @@ -461,23 +491,46 @@ 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) { std::string FilePath = testPath("foo.cpp"); addFile(FilePath, R"( - // 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; + + template + int funcTmpl(U a); + template <> + int funcTmpl(double a); + + template + int varTmpl = T(); + template <> + double varTmpl = 10.0; )"); - 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), + Children(AllOf(WithName("x"), WithKind(SymbolKind::Field)))), + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), + Children(WithName("y"))), + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), + Children()), + AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), + Children()), + AllOf(WithName("funcTmpl"), Children()), + // FIXME(ibiryukov): template args should be to match the code. + AllOf(WithName("funcTmpl"), Children()), + AllOf(WithName("varTmpl"), Children()), + // FIXME(ibiryukov): template args should be to match the code. + AllOf(WithName("varTmpl"), Children()))); } TEST_F(DocumentSymbolsTest, Namespaces) { @@ -507,10 +560,15 @@ )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")})); + ElementsAreArray>( + {AllOf(WithName("ans1"), + Children(AllOf(WithName("ai1"), Children()), + AllOf(WithName("ans2"), Children(WithName("ai2"))))), + AllOf(WithName("(anonymous namespace)"), Children(WithName("test"))), + AllOf(WithName("na"), + Children(AllOf(WithName("nb"), Children(WithName("Foo"))))), + AllOf(WithName("na"), + Children(AllOf(WithName("nb"), Children(WithName("Bar")))))})); } TEST_F(DocumentSymbolsTest, Enums) { @@ -531,10 +589,14 @@ }; } )"); - 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( + AllOf(WithName("(anonymous enum)"), Children(WithName("Red"))), + AllOf(WithName("Color"), Children(WithName("Green"))), + AllOf(WithName("Color2"), Children(WithName("Yellow"))), + AllOf(WithName("ns"), Children(AllOf(WithName("(anonymous enum)"), + Children(WithName("Black"))))))); } TEST_F(DocumentSymbolsTest, FromMacro) { @@ -553,8 +615,43 @@ 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"))); +} + +TEST_F(DocumentSymbolsTest, UsingDirectives) { + std::string FilePath = testPath("foo.cpp"); + Annotations Source(R"cpp( + namespace ns { + int foo; + } + + namespace ns_alias = ns; + + using namespace ::ns; // check we don't loose qualifiers. + using namespace ns_alias; // and namespace aliases. + )cpp"); + addFile(FilePath, Source.code()); + EXPECT_THAT(getSymbols(FilePath), + ElementsAre(WithName("ns"), WithName("ns_alias"), + WithName("using namespace ::ns"), + WithName("using namespace ns_alias"))); } } // namespace clangd Index: unittests/clangd/SyncAPI.h =================================================================== --- unittests/clangd/SyncAPI.h +++ unittests/clangd/SyncAPI.h @@ -47,8 +47,8 @@ llvm::Expected> runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit); -llvm::Expected> -runDocumentSymbols(ClangdServer &Server, PathRef File); +Expected> runDocumentSymbols(ClangdServer &Server, + PathRef File); SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query); SymbolSlab runFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req); Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -120,9 +120,9 @@ return std::move(*Result); } -Expected> -runDocumentSymbols(ClangdServer &Server, PathRef File) { - Optional>> Result; +Expected> runDocumentSymbols(ClangdServer &Server, + PathRef File) { + Optional>> Result; Server.documentSymbols(File, capture(Result)); return std::move(*Result); }