//===--- FindSymbols.cpp ------------------------------------*- C++-*------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "FindSymbols.h" #include "AST.h" #include "ClangdUnit.h" #include "FuzzyMatch.h" #include "Logger.h" #include "Quality.h" #include "SourceCode.h" #include "index/Index.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" #define DEBUG_TYPE "FindSymbols" namespace clang { namespace clangd { namespace { // Convert a index::SymbolKind to clangd::SymbolKind (LSP) // Note, some are not perfect matches and should be improved when this LSP // issue is addressed: // https://github.com/Microsoft/language-server-protocol/issues/344 SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) { switch (Kind) { case index::SymbolKind::Unknown: return SymbolKind::Variable; case index::SymbolKind::Module: return SymbolKind::Module; case index::SymbolKind::Namespace: return SymbolKind::Namespace; case index::SymbolKind::NamespaceAlias: return SymbolKind::Namespace; case index::SymbolKind::Macro: return SymbolKind::String; case index::SymbolKind::Enum: return SymbolKind::Enum; case index::SymbolKind::Struct: return SymbolKind::Struct; case index::SymbolKind::Class: return SymbolKind::Class; case index::SymbolKind::Protocol: return SymbolKind::Interface; case index::SymbolKind::Extension: return SymbolKind::Interface; case index::SymbolKind::Union: return SymbolKind::Class; case index::SymbolKind::TypeAlias: return SymbolKind::Class; case index::SymbolKind::Function: return SymbolKind::Function; case index::SymbolKind::Variable: return SymbolKind::Variable; case index::SymbolKind::Field: return SymbolKind::Field; case index::SymbolKind::EnumConstant: return SymbolKind::EnumMember; case index::SymbolKind::InstanceMethod: case index::SymbolKind::ClassMethod: case index::SymbolKind::StaticMethod: return SymbolKind::Method; case index::SymbolKind::InstanceProperty: case index::SymbolKind::ClassProperty: case index::SymbolKind::StaticProperty: return SymbolKind::Property; case index::SymbolKind::Constructor: case index::SymbolKind::Destructor: return SymbolKind::Method; case index::SymbolKind::ConversionFunction: return SymbolKind::Function; case index::SymbolKind::Parameter: return SymbolKind::Variable; case index::SymbolKind::Using: return SymbolKind::Namespace; } llvm_unreachable("invalid symbol kind"); } using ScoredSymbolInfo = std::pair; struct ScoredSymbolGreater { bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) { if (L.first != R.first) return L.first > R.first; return L.second.name < R.second.name; // Earlier name is better. } }; } // namespace llvm::Expected> getWorkspaceSymbols(StringRef Query, int Limit, const SymbolIndex *const Index, StringRef HintPath) { std::vector Result; if (Query.empty() || !Index) return Result; auto Names = splitQualifiedName(Query); FuzzyFindRequest Req; Req.Query = Names.second; // FuzzyFind doesn't want leading :: qualifier bool IsGlobalQuery = Names.first.consume_front("::"); // Restrict results to the scope in the query string if present (global or // not). if (IsGlobalQuery || !Names.first.empty()) Req.Scopes = {Names.first}; if (Limit) Req.Limit = Limit; TopN Top( Req.Limit ? *Req.Limit : std::numeric_limits::max()); FuzzyMatcher Filter(Req.Query); Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) { // Prefer the definition over e.g. a function declaration in a header auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; auto Uri = URI::parse(CD.FileURI); if (!Uri) { log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", CD.FileURI, Sym.Name); return; } auto Path = URI::resolve(*Uri, HintPath); if (!Path) { log("Workspace symbol: Could not resolve path for URI '{0}' for symbol " "'{1}'.", Uri->toString(), Sym.Name); return; } Location L; L.uri = URIForFile((*Path)); Position Start, End; Start.line = CD.Start.Line; Start.character = CD.Start.Column; End.line = CD.End.Line; End.character = CD.End.Column; L.range = {Start, End}; SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind); std::string Scope = Sym.Scope; StringRef ScopeRef = Scope; ScopeRef.consume_back("::"); SymbolInformation Info = {Sym.Name, SK, L, ScopeRef}; SymbolQualitySignals Quality; Quality.merge(Sym); SymbolRelevanceSignals Relevance; Relevance.Query = SymbolRelevanceSignals::Generic; if (auto NameMatch = Filter.match(Sym.Name)) Relevance.NameMatch = *NameMatch; else { log("Workspace symbol: {0} didn't match query {1}", Sym.Name, Filter.pattern()); return; } Relevance.merge(Sym); auto Score = evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate()); dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score, Quality, Relevance); Top.push({Score, std::move(Info)}); }); for (auto &R : std::move(Top).items()) Result.push_back(std::move(R.second)); return Result; } 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. llvm::Optional MainFileUri; public: DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {} std::vector takeSymbols() { return std::move(Symbols); } 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); } 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; } 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 = llvm::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; std::string QName = printQualifiedName(*ND); StringRef Scope, Name; std::tie(Scope, Name) = splitQualifiedName(QName); Scope.consume_back("::"); index::SymbolInfo SymInfo = index::getSymbolInfo(ND); SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); SymbolInformation SI; SI.name = Name; SI.kind = SK; SI.location = L; SI.containerName = Scope; Symbols.push_back(std::move(SI)); return true; } }; } // namespace llvm::Expected> getDocumentSymbols(ParsedAST &AST) { DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext()); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; IndexOpts.IndexFunctionLocals = false; indexTopLevelDecls(AST.getASTContext(), AST.getPreprocessor(), AST.getLocalTopLevelDecls(), DocumentSymbolsCons, IndexOpts); return DocumentSymbolsCons.takeSymbols(); } } // namespace clangd } // namespace clang