forked from OSchip/llvm-project
[clangd] Fixes for #include insertion.
Summary: o Avoid inserting a header include into the header itself. o Avoid inserting non-header files (by not indexing symbols in main files at all). o Canonicalize include paths for symbols in dynamic index. Reviewers: sammccall, ilya-biryukov Reviewed By: ilya-biryukov Subscribers: klimek, jkorous-apple, cfe-commits Differential Revision: https://reviews.llvm.org/D43462 llvm-svn: 325523
This commit is contained in:
parent
7fac6e92a7
commit
709bde886d
|
@ -327,13 +327,11 @@ ClangdServer::insertInclude(PathRef File, StringRef Code,
|
||||||
if (!Resolved)
|
if (!Resolved)
|
||||||
return Resolved.takeError();
|
return Resolved.takeError();
|
||||||
|
|
||||||
auto FS = FSProvider.getTaggedFileSystem(File).Value;
|
|
||||||
tooling::CompileCommand CompileCommand =
|
tooling::CompileCommand CompileCommand =
|
||||||
CompileArgs.getCompileCommand(File);
|
CompileArgs.getCompileCommand(File);
|
||||||
FS->setCurrentWorkingDirectory(CompileCommand.Directory);
|
|
||||||
|
|
||||||
auto Include =
|
auto Include =
|
||||||
shortenIncludePath(File, Code, *Resolved, CompileCommand, FS);
|
calculateIncludePath(File, Code, *Resolved, CompileCommand,
|
||||||
|
FSProvider.getTaggedFileSystem(File).Value);
|
||||||
if (!Include)
|
if (!Include)
|
||||||
return Include.takeError();
|
return Include.takeError();
|
||||||
if (Include->empty())
|
if (Include->empty())
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "clang/Lex/HeaderSearch.h"
|
#include "clang/Lex/HeaderSearch.h"
|
||||||
#include "clang/Lex/PreprocessorOptions.h"
|
#include "clang/Lex/PreprocessorOptions.h"
|
||||||
#include "clang/Tooling/CompilationDatabase.h"
|
#include "clang/Tooling/CompilationDatabase.h"
|
||||||
|
#include "llvm/Support/Path.h"
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace clangd {
|
namespace clangd {
|
||||||
|
@ -45,10 +46,17 @@ private:
|
||||||
/// FIXME(ioeric): we might not want to insert an absolute include path if the
|
/// FIXME(ioeric): we might not want to insert an absolute include path if the
|
||||||
/// path is not shortened.
|
/// path is not shortened.
|
||||||
llvm::Expected<std::string>
|
llvm::Expected<std::string>
|
||||||
shortenIncludePath(llvm::StringRef File, llvm::StringRef Code,
|
calculateIncludePath(llvm::StringRef File, llvm::StringRef Code,
|
||||||
llvm::StringRef Header,
|
llvm::StringRef Header,
|
||||||
const tooling::CompileCommand &CompileCommand,
|
const tooling::CompileCommand &CompileCommand,
|
||||||
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
|
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
|
||||||
|
assert(llvm::sys::path::is_absolute(File) &&
|
||||||
|
llvm::sys::path::is_absolute(Header));
|
||||||
|
|
||||||
|
if (File == Header)
|
||||||
|
return "";
|
||||||
|
FS->setCurrentWorkingDirectory(CompileCommand.Directory);
|
||||||
|
|
||||||
// Set up a CompilerInstance and create a preprocessor to collect existing
|
// Set up a CompilerInstance and create a preprocessor to collect existing
|
||||||
// #include headers in \p Code. Preprocesor also provides HeaderSearch with
|
// #include headers in \p Code. Preprocesor also provides HeaderSearch with
|
||||||
// which we can calculate the shortest include path for \p Header.
|
// which we can calculate the shortest include path for \p Header.
|
||||||
|
|
|
@ -18,18 +18,21 @@
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace clangd {
|
namespace clangd {
|
||||||
|
|
||||||
/// Determines the preferred way to #include a file, taking into account the
|
/// Determines the preferred way to #include a file, taking into account the
|
||||||
/// search path. Usually this will prefer a shorter representation like
|
/// search path. Usually this will prefer a shorter representation like
|
||||||
/// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'.
|
/// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'.
|
||||||
///
|
///
|
||||||
|
/// \param File is an absolute file path.
|
||||||
/// \param Header is an absolute file path.
|
/// \param Header is an absolute file path.
|
||||||
/// \return A quoted "path" or <path>. If \p Header is already (directly)
|
/// \return A quoted "path" or <path>. This returns an empty string if:
|
||||||
/// included in the file (including those included via different paths), this
|
/// - \p Header is already (directly) included in the file (including those
|
||||||
/// returns an empty string.
|
/// included via different paths).
|
||||||
|
/// - \p Header is the same as \p File.
|
||||||
llvm::Expected<std::string>
|
llvm::Expected<std::string>
|
||||||
shortenIncludePath(PathRef File, llvm::StringRef Code, llvm::StringRef Header,
|
calculateIncludePath(PathRef File, llvm::StringRef Code, llvm::StringRef Header,
|
||||||
const tooling::CompileCommand &CompileCommand,
|
const tooling::CompileCommand &CompileCommand,
|
||||||
IntrusiveRefCntPtr<vfs::FileSystem> FS);
|
IntrusiveRefCntPtr<vfs::FileSystem> FS);
|
||||||
|
|
||||||
} // namespace clangd
|
} // namespace clangd
|
||||||
} // namespace clang
|
} // namespace clang
|
||||||
|
|
|
@ -28,6 +28,7 @@ void CanonicalIncludes::addRegexMapping(llvm::StringRef RE,
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::StringRef CanonicalIncludes::mapHeader(llvm::StringRef Header) const {
|
llvm::StringRef CanonicalIncludes::mapHeader(llvm::StringRef Header) const {
|
||||||
|
std::lock_guard<std::mutex> Lock(RegexMutex);
|
||||||
for (auto &Entry : RegexHeaderMappingTable) {
|
for (auto &Entry : RegexHeaderMappingTable) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
std::string Dummy;
|
std::string Dummy;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "clang/Lex/Preprocessor.h"
|
#include "clang/Lex/Preprocessor.h"
|
||||||
#include "llvm/ADT/StringMap.h"
|
#include "llvm/ADT/StringMap.h"
|
||||||
#include "llvm/Support/Regex.h"
|
#include "llvm/Support/Regex.h"
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ namespace clangd {
|
||||||
|
|
||||||
/// Maps a definition location onto an #include file, based on a set of filename
|
/// Maps a definition location onto an #include file, based on a set of filename
|
||||||
/// rules.
|
/// rules.
|
||||||
|
/// Only const methods (i.e. mapHeader) in this class are thread safe.
|
||||||
class CanonicalIncludes {
|
class CanonicalIncludes {
|
||||||
public:
|
public:
|
||||||
CanonicalIncludes() = default;
|
CanonicalIncludes() = default;
|
||||||
|
@ -53,6 +55,8 @@ private:
|
||||||
// arbitrary regexes.
|
// arbitrary regexes.
|
||||||
mutable std::vector<std::pair<llvm::Regex, std::string>>
|
mutable std::vector<std::pair<llvm::Regex, std::string>>
|
||||||
RegexHeaderMappingTable;
|
RegexHeaderMappingTable;
|
||||||
|
// Guards Regex matching as it's not thread-safe.
|
||||||
|
mutable std::mutex RegexMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns a CommentHandler that parses pragma comment on include files to
|
/// Returns a CommentHandler that parses pragma comment on include files to
|
||||||
|
|
|
@ -15,14 +15,30 @@ namespace clang {
|
||||||
namespace clangd {
|
namespace clangd {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
const CanonicalIncludes *canonicalIncludesForSystemHeaders() {
|
||||||
|
static const auto *Includes = [] {
|
||||||
|
auto *I = new CanonicalIncludes();
|
||||||
|
addSystemHeadersMapping(I);
|
||||||
|
return I;
|
||||||
|
}();
|
||||||
|
return Includes;
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves namespace and class level symbols in \p Decls.
|
/// Retrieves namespace and class level symbols in \p Decls.
|
||||||
std::unique_ptr<SymbolSlab> indexAST(ASTContext &Ctx,
|
std::unique_ptr<SymbolSlab> indexAST(ASTContext &Ctx,
|
||||||
std::shared_ptr<Preprocessor> PP,
|
std::shared_ptr<Preprocessor> PP,
|
||||||
llvm::ArrayRef<const Decl *> Decls) {
|
llvm::ArrayRef<const Decl *> Decls) {
|
||||||
SymbolCollector::Options CollectorOpts;
|
SymbolCollector::Options CollectorOpts;
|
||||||
// Code completion gets main-file results from Sema.
|
// Although we do not index symbols in main files (e.g. cpp file), information
|
||||||
// But we leave this option on because features like go-to-definition want it.
|
// in main files like definition locations of class declarations will still be
|
||||||
CollectorOpts.IndexMainFiles = true;
|
// collected; thus, the index works for go-to-definition.
|
||||||
|
// FIXME(ioeric): handle IWYU pragma for dynamic index. We might want to make
|
||||||
|
// SymbolCollector always provides include canonicalization (e.g. IWYU, STL).
|
||||||
|
// FIXME(ioeric): get rid of `IndexMainFiles` as this is always set to false.
|
||||||
|
CollectorOpts.IndexMainFiles = false;
|
||||||
|
CollectorOpts.CollectIncludePath = true;
|
||||||
|
CollectorOpts.Includes = canonicalIncludesForSystemHeaders();
|
||||||
|
|
||||||
auto Collector = std::make_shared<SymbolCollector>(std::move(CollectorOpts));
|
auto Collector = std::make_shared<SymbolCollector>(std::move(CollectorOpts));
|
||||||
Collector->setPreprocessor(std::move(PP));
|
Collector->setPreprocessor(std::move(PP));
|
||||||
index::IndexingOptions IndexOpts;
|
index::IndexingOptions IndexOpts;
|
||||||
|
|
|
@ -580,8 +580,11 @@ TEST(CompletionTest, DynamicIndexMultiFile) {
|
||||||
/*StorePreamblesInMemory=*/true,
|
/*StorePreamblesInMemory=*/true,
|
||||||
/*BuildDynamicSymbolIndex=*/true);
|
/*BuildDynamicSymbolIndex=*/true);
|
||||||
|
|
||||||
Server.addDocument(testPath("foo.cpp"), R"cpp(
|
FS.Files[testPath("foo.h")] = R"cpp(
|
||||||
namespace ns { class XYZ {}; void foo(int x) {} }
|
namespace ns { class XYZ {}; void foo(int x) {} }
|
||||||
|
)cpp";
|
||||||
|
Server.addDocument(testPath("foo.cpp"), R"cpp(
|
||||||
|
#include "foo.h"
|
||||||
)cpp");
|
)cpp");
|
||||||
ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble";
|
ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble";
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#include "TestFS.h"
|
||||||
#include "index/FileIndex.h"
|
#include "index/FileIndex.h"
|
||||||
#include "clang/Frontend/CompilerInvocation.h"
|
#include "clang/Frontend/CompilerInvocation.h"
|
||||||
#include "clang/Frontend/PCHContainerOperations.h"
|
#include "clang/Frontend/PCHContainerOperations.h"
|
||||||
|
@ -83,17 +84,28 @@ std::vector<std::string> match(const SymbolIndex &I,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an ParsedAST for \p Code. Returns None if \p Code is empty.
|
/// Create an ParsedAST for \p Code. Returns None if \p Code is empty.
|
||||||
llvm::Optional<ParsedAST> build(std::string Path, llvm::StringRef Code) {
|
/// \p Code is put into <Path>.h which is included by \p <BasePath>.cpp.
|
||||||
|
llvm::Optional<ParsedAST> build(llvm::StringRef BasePath,
|
||||||
|
llvm::StringRef Code) {
|
||||||
if (Code.empty())
|
if (Code.empty())
|
||||||
return llvm::None;
|
return llvm::None;
|
||||||
const char *Args[] = {"clang", "-xc++", Path.c_str()};
|
|
||||||
|
assert(llvm::sys::path::extension(BasePath).empty() &&
|
||||||
|
"BasePath must be a base file path without extension.");
|
||||||
|
llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> VFS(
|
||||||
|
new vfs::InMemoryFileSystem);
|
||||||
|
std::string Path = (BasePath + ".cpp").str();
|
||||||
|
std::string Header = (BasePath + ".h").str();
|
||||||
|
VFS->addFile(Path, 0, llvm::MemoryBuffer::getMemBuffer(""));
|
||||||
|
VFS->addFile(Header, 0, llvm::MemoryBuffer::getMemBuffer(Code));
|
||||||
|
const char *Args[] = {"clang", "-xc++", "-include", Header.c_str(),
|
||||||
|
Path.c_str()};
|
||||||
|
|
||||||
auto CI = createInvocationFromCommandLine(Args);
|
auto CI = createInvocationFromCommandLine(Args);
|
||||||
|
|
||||||
auto Buf = llvm::MemoryBuffer::getMemBuffer(Code);
|
auto Buf = llvm::MemoryBuffer::getMemBuffer(Code);
|
||||||
auto AST = ParsedAST::Build(std::move(CI), nullptr, std::move(Buf),
|
auto AST = ParsedAST::Build(std::move(CI), nullptr, std::move(Buf),
|
||||||
std::make_shared<PCHContainerOperations>(),
|
std::make_shared<PCHContainerOperations>(), VFS);
|
||||||
vfs::getRealFileSystem());
|
|
||||||
assert(AST.hasValue());
|
assert(AST.hasValue());
|
||||||
return std::move(*AST);
|
return std::move(*AST);
|
||||||
}
|
}
|
||||||
|
@ -169,6 +181,20 @@ TEST(FileIndexTest, IgnoreClassMembers) {
|
||||||
EXPECT_THAT(match(M, Req), UnorderedElementsAre("X"));
|
EXPECT_THAT(match(M, Req), UnorderedElementsAre("X"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef LLVM_ON_WIN32
|
||||||
|
TEST(FileIndexTest, CanonicalizeSystemHeader) {
|
||||||
|
FileIndex M;
|
||||||
|
std::string File = testPath("bits/basic_string");
|
||||||
|
M.update(File, build(File, "class string {};").getPointer());
|
||||||
|
|
||||||
|
FuzzyFindRequest Req;
|
||||||
|
Req.Query = "";
|
||||||
|
M.fuzzyFind(Req, [&](const Symbol &Sym) {
|
||||||
|
EXPECT_EQ(Sym.Detail->IncludeHeader, "<string>");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace clangd
|
} // namespace clangd
|
||||||
} // namespace clang
|
} // namespace clang
|
||||||
|
|
|
@ -24,24 +24,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Adds \p File with content \p Code to the sub directory and returns the
|
// Calculates the include path for \p Header, or returns "" on error.
|
||||||
// absolute path.
|
std::string calculate(PathRef Header) {
|
||||||
// std::string addToSubdir(PathRef File, llvm::StringRef Code = "") {
|
|
||||||
// assert(llvm::sys::path::is_relative(File) && "FileName should be relative");
|
|
||||||
// llvm::SmallString<32> Path;
|
|
||||||
// llvm::sys::path::append(Path, Subdir, File);
|
|
||||||
// FS.Files[Path] = Code;
|
|
||||||
// return Path.str();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Shortens the header and returns "" on error.
|
|
||||||
std::string shorten(PathRef Header) {
|
|
||||||
auto VFS = FS.getTaggedFileSystem(MainFile).Value;
|
auto VFS = FS.getTaggedFileSystem(MainFile).Value;
|
||||||
auto Cmd = CDB.getCompileCommand(MainFile);
|
auto Cmd = CDB.getCompileCommand(MainFile);
|
||||||
assert(static_cast<bool>(Cmd));
|
assert(static_cast<bool>(Cmd));
|
||||||
VFS->setCurrentWorkingDirectory(Cmd->Directory);
|
VFS->setCurrentWorkingDirectory(Cmd->Directory);
|
||||||
auto Path =
|
auto Path =
|
||||||
shortenIncludePath(MainFile, FS.Files[MainFile], Header, *Cmd, VFS);
|
calculateIncludePath(MainFile, FS.Files[MainFile], Header, *Cmd, VFS);
|
||||||
if (!Path) {
|
if (!Path) {
|
||||||
llvm::consumeError(Path.takeError());
|
llvm::consumeError(Path.takeError());
|
||||||
return std::string();
|
return std::string();
|
||||||
|
@ -58,7 +48,7 @@ protected:
|
||||||
TEST_F(HeadersTest, InsertInclude) {
|
TEST_F(HeadersTest, InsertInclude) {
|
||||||
std::string Path = testPath("sub/bar.h");
|
std::string Path = testPath("sub/bar.h");
|
||||||
FS.Files[Path] = "";
|
FS.Files[Path] = "";
|
||||||
EXPECT_EQ(shorten(Path), "\"bar.h\"");
|
EXPECT_EQ(calculate(Path), "\"bar.h\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(HeadersTest, DontInsertDuplicateSameName) {
|
TEST_F(HeadersTest, DontInsertDuplicateSameName) {
|
||||||
|
@ -67,7 +57,7 @@ TEST_F(HeadersTest, DontInsertDuplicateSameName) {
|
||||||
)cpp";
|
)cpp";
|
||||||
std::string BarHeader = testPath("sub/bar.h");
|
std::string BarHeader = testPath("sub/bar.h");
|
||||||
FS.Files[BarHeader] = "";
|
FS.Files[BarHeader] = "";
|
||||||
EXPECT_EQ(shorten(BarHeader), "");
|
EXPECT_EQ(calculate(BarHeader), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(HeadersTest, DontInsertDuplicateDifferentName) {
|
TEST_F(HeadersTest, DontInsertDuplicateDifferentName) {
|
||||||
|
@ -76,7 +66,7 @@ TEST_F(HeadersTest, DontInsertDuplicateDifferentName) {
|
||||||
FS.Files[MainFile] = R"cpp(
|
FS.Files[MainFile] = R"cpp(
|
||||||
#include "sub/bar.h" // not shortest
|
#include "sub/bar.h" // not shortest
|
||||||
)cpp";
|
)cpp";
|
||||||
EXPECT_EQ(shorten(BarHeader), "");
|
EXPECT_EQ(calculate(BarHeader), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(HeadersTest, StillInsertIfTrasitivelyIncluded) {
|
TEST_F(HeadersTest, StillInsertIfTrasitivelyIncluded) {
|
||||||
|
@ -89,7 +79,13 @@ TEST_F(HeadersTest, StillInsertIfTrasitivelyIncluded) {
|
||||||
FS.Files[MainFile] = R"cpp(
|
FS.Files[MainFile] = R"cpp(
|
||||||
#include "bar.h"
|
#include "bar.h"
|
||||||
)cpp";
|
)cpp";
|
||||||
EXPECT_EQ(shorten(BazHeader), "\"baz.h\"");
|
EXPECT_EQ(calculate(BazHeader), "\"baz.h\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HeadersTest, DoNotInsertIfInSameFile) {
|
||||||
|
MainFile = testPath("main.h");
|
||||||
|
FS.Files[MainFile] = "";
|
||||||
|
EXPECT_EQ(calculate(MainFile), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
Loading…
Reference in New Issue