| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 1 | //===--- SymbolCollector.cpp -------------------------------------*- C++-*-===// | 
|  | 2 | // | 
|  | 3 | //                     The LLVM Compiler Infrastructure | 
|  | 4 | // | 
|  | 5 | // This file is distributed under the University of Illinois Open Source | 
|  | 6 | // License. See LICENSE.TXT for details. | 
|  | 7 | // | 
|  | 8 | //===----------------------------------------------------------------------===// | 
|  | 9 |  | 
|  | 10 | #include "SymbolCollector.h" | 
| Eric Liu | 76f6b44 | 2018-01-09 17:32:00 +0000 | [diff] [blame] | 11 | #include "../CodeCompletionStrings.h" | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 12 | #include "clang/AST/DeclCXX.h" | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 13 | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 14 | #include "clang/Basic/SourceManager.h" | 
|  | 15 | #include "clang/Index/IndexSymbol.h" | 
|  | 16 | #include "clang/Index/USRGeneration.h" | 
|  | 17 | #include "llvm/Support/MemoryBuffer.h" | 
|  | 18 | #include "llvm/Support/Path.h" | 
|  | 19 |  | 
|  | 20 | namespace clang { | 
|  | 21 | namespace clangd { | 
|  | 22 |  | 
|  | 23 | namespace { | 
|  | 24 | // Make the Path absolute using the current working directory of the given | 
|  | 25 | // SourceManager if the Path is not an absolute path. | 
|  | 26 | // | 
|  | 27 | // The Path can be a path relative to the build directory, or retrieved from | 
|  | 28 | // the SourceManager. | 
|  | 29 | std::string makeAbsolutePath(const SourceManager &SM, StringRef Path) { | 
|  | 30 | llvm::SmallString<128> AbsolutePath(Path); | 
|  | 31 | if (std::error_code EC = | 
|  | 32 | SM.getFileManager().getVirtualFileSystem()->makeAbsolute( | 
|  | 33 | AbsolutePath)) | 
|  | 34 | llvm::errs() << "Warning: could not make absolute file: '" << EC.message() | 
|  | 35 | << '\n'; | 
|  | 36 | // Handle the symbolic link path case where the current working directory | 
|  | 37 | // (getCurrentWorkingDirectory) is a symlink./ We always want to the real | 
|  | 38 | // file path (instead of the symlink path) for the  C++ symbols. | 
|  | 39 | // | 
|  | 40 | // Consider the following example: | 
|  | 41 | // | 
|  | 42 | //   src dir: /project/src/foo.h | 
|  | 43 | //   current working directory (symlink): /tmp/build -> /project/src/ | 
|  | 44 | // | 
|  | 45 | // The file path of Symbol is "/project/src/foo.h" instead of | 
|  | 46 | // "/tmp/build/foo.h" | 
|  | 47 | const DirectoryEntry *Dir = SM.getFileManager().getDirectory( | 
|  | 48 | llvm::sys::path::parent_path(AbsolutePath.str())); | 
|  | 49 | if (Dir) { | 
|  | 50 | StringRef DirName = SM.getFileManager().getCanonicalName(Dir); | 
| Benjamin Kramer | 50a967d | 2017-12-28 14:47:01 +0000 | [diff] [blame] | 51 | SmallString<128> AbsoluteFilename; | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 52 | llvm::sys::path::append(AbsoluteFilename, DirName, | 
|  | 53 | llvm::sys::path::filename(AbsolutePath.str())); | 
| Benjamin Kramer | 50a967d | 2017-12-28 14:47:01 +0000 | [diff] [blame] | 54 | return AbsoluteFilename.str(); | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 55 | } | 
|  | 56 | return AbsolutePath.str(); | 
|  | 57 | } | 
| Eric Liu | 4feda80 | 2017-12-19 11:37:40 +0000 | [diff] [blame] | 58 |  | 
| Eric Liu | 4feda80 | 2017-12-19 11:37:40 +0000 | [diff] [blame] | 59 | // "a::b::c", return {"a::b", "c"}. Scope is empty if it doesn't exist. | 
|  | 60 | std::pair<llvm::StringRef, llvm::StringRef> | 
|  | 61 | splitQualifiedName(llvm::StringRef QName) { | 
|  | 62 | assert(!QName.startswith("::") && "Qualified names should not start with ::"); | 
|  | 63 | size_t Pos = QName.rfind("::"); | 
|  | 64 | if (Pos == llvm::StringRef::npos) | 
|  | 65 | return {StringRef(), QName}; | 
|  | 66 | return {QName.substr(0, Pos), QName.substr(Pos + 2)}; | 
|  | 67 | } | 
|  | 68 |  | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 69 | bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx, | 
|  | 70 | const SymbolCollector::Options &Opts) { | 
|  | 71 | using namespace clang::ast_matchers; | 
|  | 72 | if (ND->isImplicit()) | 
|  | 73 | return true; | 
| Haojian Wu | 9873fdd | 2018-01-19 09:35:55 +0000 | [diff] [blame^] | 74 | // Skip anonymous declarations, e.g (anonymous enum/class/struct). | 
|  | 75 | if (ND->getDeclName().isEmpty()) | 
|  | 76 | return true; | 
|  | 77 |  | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 78 | // FIXME: figure out a way to handle internal linkage symbols (e.g. static | 
|  | 79 | // variables, function) defined in the .cc files. Also we skip the symbols | 
|  | 80 | // in anonymous namespace as the qualifier names of these symbols are like | 
|  | 81 | // `foo::<anonymous>::bar`, which need a special handling. | 
|  | 82 | // In real world projects, we have a relatively large set of header files | 
|  | 83 | // that define static variables (like "static const int A = 1;"), we still | 
|  | 84 | // want to collect these symbols, although they cause potential ODR | 
|  | 85 | // violations. | 
|  | 86 | if (ND->isInAnonymousNamespace()) | 
|  | 87 | return true; | 
|  | 88 |  | 
| Haojian Wu | 9873fdd | 2018-01-19 09:35:55 +0000 | [diff] [blame^] | 89 | // We only want: | 
|  | 90 | //   * symbols in namespaces or translation unit scopes (e.g. no class | 
|  | 91 | //     members) | 
|  | 92 | //   * enum constants in unscoped enum decl (e.g. "red" in "enum {red};") | 
|  | 93 | auto InTopLevelScope = | 
|  | 94 | hasDeclContext(anyOf(namespaceDecl(), translationUnitDecl())); | 
|  | 95 | if (match(decl(allOf(Opts.IndexMainFiles | 
|  | 96 | ? decl() | 
|  | 97 | : decl(unless(isExpansionInMainFile())), | 
|  | 98 | anyOf(InTopLevelScope, | 
|  | 99 | hasDeclContext(enumDecl(InTopLevelScope, | 
|  | 100 | unless(isScoped())))))), | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 101 | *ND, *ASTCtx) | 
|  | 102 | .empty()) | 
|  | 103 | return true; | 
|  | 104 |  | 
|  | 105 | return false; | 
|  | 106 | } | 
|  | 107 |  | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 108 | } // namespace | 
|  | 109 |  | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 110 | SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {} | 
|  | 111 |  | 
| Eric Liu | 76f6b44 | 2018-01-09 17:32:00 +0000 | [diff] [blame] | 112 | void SymbolCollector::initialize(ASTContext &Ctx) { | 
|  | 113 | ASTCtx = &Ctx; | 
|  | 114 | CompletionAllocator = std::make_shared<GlobalCodeCompletionAllocator>(); | 
|  | 115 | CompletionTUInfo = | 
|  | 116 | llvm::make_unique<CodeCompletionTUInfo>(CompletionAllocator); | 
|  | 117 | } | 
|  | 118 |  | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 119 | // Always return true to continue indexing. | 
|  | 120 | bool SymbolCollector::handleDeclOccurence( | 
|  | 121 | const Decl *D, index::SymbolRoleSet Roles, | 
|  | 122 | ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset, | 
|  | 123 | index::IndexDataConsumer::ASTNodeInfo ASTNode) { | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 124 | assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); | 
|  | 125 |  | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 126 | // FIXME: collect all symbol references. | 
|  | 127 | if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) || | 
|  | 128 | Roles & static_cast<unsigned>(index::SymbolRole::Definition))) | 
|  | 129 | return true; | 
|  | 130 |  | 
| Eric Liu | 76f6b44 | 2018-01-09 17:32:00 +0000 | [diff] [blame] | 131 | assert(CompletionAllocator && CompletionTUInfo); | 
|  | 132 |  | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 133 | if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D)) { | 
| Eric Liu | 9af958f | 2018-01-10 14:57:58 +0000 | [diff] [blame] | 134 | if (shouldFilterDecl(ND, ASTCtx, Opts)) | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 135 | return true; | 
| Benjamin Kramer | 50a967d | 2017-12-28 14:47:01 +0000 | [diff] [blame] | 136 | llvm::SmallString<128> USR; | 
|  | 137 | if (index::generateUSRForDecl(ND, USR)) | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 138 | return true; | 
|  | 139 |  | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 140 | auto ID = SymbolID(USR); | 
| Sam McCall | 4b9bbb3 | 2017-12-23 19:38:03 +0000 | [diff] [blame] | 141 | if (Symbols.find(ID) != nullptr) | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 142 | return true; | 
|  | 143 |  | 
|  | 144 | auto &SM = ND->getASTContext().getSourceManager(); | 
| Benjamin Kramer | 6452efd | 2017-12-21 17:51:35 +0000 | [diff] [blame] | 145 | std::string FilePath = | 
|  | 146 | makeAbsolutePath(SM, SM.getFilename(D->getLocation())); | 
|  | 147 | SymbolLocation Location = {FilePath, SM.getFileOffset(D->getLocStart()), | 
|  | 148 | SM.getFileOffset(D->getLocEnd())}; | 
| Eric Liu | 4feda80 | 2017-12-19 11:37:40 +0000 | [diff] [blame] | 149 | std::string QName = ND->getQualifiedNameAsString(); | 
|  | 150 | auto ScopeAndName = splitQualifiedName(QName); | 
| Sam McCall | 98a7318 | 2017-12-22 08:12:39 +0000 | [diff] [blame] | 151 |  | 
|  | 152 | Symbol S; | 
|  | 153 | S.ID = std::move(ID); | 
|  | 154 | S.Scope = ScopeAndName.first; | 
|  | 155 | S.Name = ScopeAndName.second; | 
|  | 156 | S.SymInfo = index::getSymbolInfo(D); | 
|  | 157 | S.CanonicalDeclaration = Location; | 
| Eric Liu | 76f6b44 | 2018-01-09 17:32:00 +0000 | [diff] [blame] | 158 |  | 
|  | 159 | // Add completion info. | 
|  | 160 | assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); | 
|  | 161 | CodeCompletionResult SymbolCompletion(ND, 0); | 
|  | 162 | const auto *CCS = SymbolCompletion.CreateCodeCompletionString( | 
|  | 163 | *ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator, | 
|  | 164 | *CompletionTUInfo, | 
|  | 165 | /*IncludeBriefComments*/ true); | 
|  | 166 | std::string Label; | 
|  | 167 | std::string SnippetInsertText; | 
|  | 168 | std::string IgnoredLabel; | 
|  | 169 | std::string PlainInsertText; | 
|  | 170 | getLabelAndInsertText(*CCS, &Label, &SnippetInsertText, | 
|  | 171 | /*EnableSnippets=*/true); | 
|  | 172 | getLabelAndInsertText(*CCS, &IgnoredLabel, &PlainInsertText, | 
|  | 173 | /*EnableSnippets=*/false); | 
|  | 174 | std::string FilterText = getFilterText(*CCS); | 
|  | 175 | std::string Documentation = getDocumentation(*CCS); | 
|  | 176 | std::string CompletionDetail = getDetail(*CCS); | 
|  | 177 |  | 
|  | 178 | S.CompletionFilterText = FilterText; | 
|  | 179 | S.CompletionLabel = Label; | 
|  | 180 | S.CompletionPlainInsertText = PlainInsertText; | 
|  | 181 | S.CompletionSnippetInsertText = SnippetInsertText; | 
|  | 182 | Symbol::Details Detail; | 
|  | 183 | Detail.Documentation = Documentation; | 
|  | 184 | Detail.CompletionDetail = CompletionDetail; | 
|  | 185 | S.Detail = &Detail; | 
|  | 186 |  | 
| Sam McCall | 98a7318 | 2017-12-22 08:12:39 +0000 | [diff] [blame] | 187 | Symbols.insert(S); | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 188 | } | 
|  | 189 |  | 
|  | 190 | return true; | 
|  | 191 | } | 
|  | 192 |  | 
| Haojian Wu | 4c1394d | 2017-12-12 15:42:10 +0000 | [diff] [blame] | 193 | } // namespace clangd | 
|  | 194 | } // namespace clang |