blob: 92402c5ec6c0284795f548e86e59ff03cbc04fbb [file] [log] [blame]
Haojian Wu4c1394d2017-12-12 15:42:10 +00001//===--- 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 Liu76f6b442018-01-09 17:32:00 +000011#include "../CodeCompletionStrings.h"
Haojian Wu4c1394d2017-12-12 15:42:10 +000012#include "clang/AST/DeclCXX.h"
Eric Liu9af958f2018-01-10 14:57:58 +000013#include "clang/ASTMatchers/ASTMatchFinder.h"
Haojian Wu4c1394d2017-12-12 15:42:10 +000014#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
20namespace clang {
21namespace clangd {
22
23namespace {
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.
29std::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 Kramer50a967d2017-12-28 14:47:01 +000051 SmallString<128> AbsoluteFilename;
Haojian Wu4c1394d2017-12-12 15:42:10 +000052 llvm::sys::path::append(AbsoluteFilename, DirName,
53 llvm::sys::path::filename(AbsolutePath.str()));
Benjamin Kramer50a967d2017-12-28 14:47:01 +000054 return AbsoluteFilename.str();
Haojian Wu4c1394d2017-12-12 15:42:10 +000055 }
56 return AbsolutePath.str();
57}
Eric Liu4feda802017-12-19 11:37:40 +000058
Eric Liu4feda802017-12-19 11:37:40 +000059// "a::b::c", return {"a::b", "c"}. Scope is empty if it doesn't exist.
60std::pair<llvm::StringRef, llvm::StringRef>
61splitQualifiedName(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 Liu9af958f2018-01-10 14:57:58 +000069bool 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 Wu9873fdd2018-01-19 09:35:55 +000074 // Skip anonymous declarations, e.g (anonymous enum/class/struct).
75 if (ND->getDeclName().isEmpty())
76 return true;
77
Eric Liu9af958f2018-01-10 14:57:58 +000078 // 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 Wu9873fdd2018-01-19 09:35:55 +000089 // 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 Liu9af958f2018-01-10 14:57:58 +0000101 *ND, *ASTCtx)
102 .empty())
103 return true;
104
105 return false;
106}
107
Haojian Wu4c1394d2017-12-12 15:42:10 +0000108} // namespace
109
Eric Liu9af958f2018-01-10 14:57:58 +0000110SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {}
111
Eric Liu76f6b442018-01-09 17:32:00 +0000112void SymbolCollector::initialize(ASTContext &Ctx) {
113 ASTCtx = &Ctx;
114 CompletionAllocator = std::make_shared<GlobalCodeCompletionAllocator>();
115 CompletionTUInfo =
116 llvm::make_unique<CodeCompletionTUInfo>(CompletionAllocator);
117}
118
Haojian Wu4c1394d2017-12-12 15:42:10 +0000119// Always return true to continue indexing.
120bool SymbolCollector::handleDeclOccurence(
121 const Decl *D, index::SymbolRoleSet Roles,
122 ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset,
123 index::IndexDataConsumer::ASTNodeInfo ASTNode) {
Eric Liu9af958f2018-01-10 14:57:58 +0000124 assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set.");
125
Haojian Wu4c1394d2017-12-12 15:42:10 +0000126 // 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 Liu76f6b442018-01-09 17:32:00 +0000131 assert(CompletionAllocator && CompletionTUInfo);
132
Haojian Wu4c1394d2017-12-12 15:42:10 +0000133 if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D)) {
Eric Liu9af958f2018-01-10 14:57:58 +0000134 if (shouldFilterDecl(ND, ASTCtx, Opts))
Haojian Wu4c1394d2017-12-12 15:42:10 +0000135 return true;
Benjamin Kramer50a967d2017-12-28 14:47:01 +0000136 llvm::SmallString<128> USR;
137 if (index::generateUSRForDecl(ND, USR))
Haojian Wu4c1394d2017-12-12 15:42:10 +0000138 return true;
139
Haojian Wu4c1394d2017-12-12 15:42:10 +0000140 auto ID = SymbolID(USR);
Sam McCall4b9bbb32017-12-23 19:38:03 +0000141 if (Symbols.find(ID) != nullptr)
Haojian Wu4c1394d2017-12-12 15:42:10 +0000142 return true;
143
144 auto &SM = ND->getASTContext().getSourceManager();
Benjamin Kramer6452efd2017-12-21 17:51:35 +0000145 std::string FilePath =
146 makeAbsolutePath(SM, SM.getFilename(D->getLocation()));
147 SymbolLocation Location = {FilePath, SM.getFileOffset(D->getLocStart()),
148 SM.getFileOffset(D->getLocEnd())};
Eric Liu4feda802017-12-19 11:37:40 +0000149 std::string QName = ND->getQualifiedNameAsString();
150 auto ScopeAndName = splitQualifiedName(QName);
Sam McCall98a73182017-12-22 08:12:39 +0000151
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 Liu76f6b442018-01-09 17:32:00 +0000158
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 McCall98a73182017-12-22 08:12:39 +0000187 Symbols.insert(S);
Haojian Wu4c1394d2017-12-12 15:42:10 +0000188 }
189
190 return true;
191}
192
Haojian Wu4c1394d2017-12-12 15:42:10 +0000193} // namespace clangd
194} // namespace clang