diff options
-rw-r--r-- | clang-tools-extra/clangd/FindSymbols.cpp | 2 | ||||
-rw-r--r-- | clang-tools-extra/clangd/SourceCode.cpp | 34 | ||||
-rw-r--r-- | clang-tools-extra/clangd/SourceCode.h | 15 | ||||
-rw-r--r-- | clang-tools-extra/clangd/XRefs.cpp | 4 | ||||
-rw-r--r-- | clang-tools-extra/unittests/clangd/TestFS.cpp | 26 | ||||
-rw-r--r-- | clang-tools-extra/unittests/clangd/TestFS.h | 15 | ||||
-rw-r--r-- | clang-tools-extra/unittests/clangd/XRefsTests.cpp | 50 |
7 files changed, 114 insertions, 32 deletions
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index c1ee678977d..cc7f084f26a 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -193,7 +193,7 @@ public: const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID()); if (!F) return; - auto FilePath = getAbsoluteFilePath(F, SM); + auto FilePath = getRealPath(F, SM); if (FilePath) MainFileUri = URIForFile(*FilePath); } diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp index 88ec2c95683..931ad3c0f20 100644 --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -185,18 +185,34 @@ std::vector<TextEdit> replacementsToEdits(StringRef Code, return Edits; } -llvm::Optional<std::string> -getAbsoluteFilePath(const FileEntry *F, const SourceManager &SourceMgr) { - SmallString<64> FilePath = F->tryGetRealPathName(); - if (FilePath.empty()) - FilePath = F->getName(); - if (!llvm::sys::path::is_absolute(FilePath)) { - if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) { - log("Could not turn relative path to absolute: {0}", FilePath); +llvm::Optional<std::string> getRealPath(const FileEntry *F, + const SourceManager &SourceMgr) { + // Ideally, we get the real path from the FileEntry object. + SmallString<128> FilePath = F->tryGetRealPathName(); + if (!FilePath.empty()) { + return FilePath.str().str(); + } + + // Otherwise, we try to compute ourselves. + vlog("FileEntry for {0} did not contain the real path.", F->getName()); + + llvm::SmallString<128> Path = F->getName(); + + if (!llvm::sys::path::is_absolute(Path)) { + if (!SourceMgr.getFileManager().makeAbsolutePath(Path)) { + log("Could not turn relative path to absolute: {0}", Path); return llvm::None; } } - return FilePath.str().str(); + + llvm::SmallString<128> RealPath; + if (SourceMgr.getFileManager().getVirtualFileSystem()->getRealPath( + Path, RealPath)) { + log("Could not compute real path: {0}", Path); + return Path.str().str(); + } + + return RealPath.str().str(); } TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M, diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h index cb0c6728781..e633a1570d1 100644 --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -62,13 +62,20 @@ TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R); std::vector<TextEdit> replacementsToEdits(StringRef Code, const tooling::Replacements &Repls); -/// Get the absolute file path of a given file entry. -llvm::Optional<std::string> getAbsoluteFilePath(const FileEntry *F, - const SourceManager &SourceMgr); - TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M, const LangOptions &L); +/// Get the real/canonical path of \p F. This means: +/// +/// - Absolute path +/// - Symlinks resolved +/// - No "." or ".." component +/// - No duplicate or trailing directory separator +/// +/// This function should be used when sending paths to clients, so that paths +/// are normalized as much as possible. +llvm::Optional<std::string> getRealPath(const FileEntry *F, + const SourceManager &SourceMgr); } // namespace clangd } // namespace clang #endif diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index e95730ba7e8..f5e2b1e12da 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -192,7 +192,7 @@ makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) { Range R = {Begin, End}; Location L; - auto FilePath = getAbsoluteFilePath(F, SourceMgr); + auto FilePath = getRealPath(F, SourceMgr); if (!FilePath) { log("failed to get path!"); return llvm::None; @@ -286,7 +286,7 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos, std::string HintPath; const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); - if (auto Path = getAbsoluteFilePath(FE, SourceMgr)) + if (auto Path = getRealPath(FE, SourceMgr)) HintPath = *Path; // Query the index and populate the empty slot. Index->lookup( diff --git a/clang-tools-extra/unittests/clangd/TestFS.cpp b/clang-tools-extra/unittests/clangd/TestFS.cpp index b3081d6430b..d3112b92278 100644 --- a/clang-tools-extra/unittests/clangd/TestFS.cpp +++ b/clang-tools-extra/unittests/clangd/TestFS.cpp @@ -32,8 +32,10 @@ buildTestFS(llvm::StringMap<std::string> const &Files, return MemFS; } -MockCompilationDatabase::MockCompilationDatabase(bool UseRelPaths) - : ExtraClangFlags({"-ffreestanding"}), UseRelPaths(UseRelPaths) { +MockCompilationDatabase::MockCompilationDatabase(StringRef Directory, + StringRef RelPathPrefix) + : ExtraClangFlags({"-ffreestanding"}), Directory(Directory), + RelPathPrefix(RelPathPrefix) { // -ffreestanding avoids implicit stdc-predef.h. } @@ -42,12 +44,24 @@ MockCompilationDatabase::getCompileCommand(PathRef File) const { if (ExtraClangFlags.empty()) return None; - auto CommandLine = ExtraClangFlags; auto FileName = sys::path::filename(File); + + // Build the compile command. + auto CommandLine = ExtraClangFlags; CommandLine.insert(CommandLine.begin(), "clang"); - CommandLine.insert(CommandLine.end(), UseRelPaths ? FileName : File); - return {tooling::CompileCommand(sys::path::parent_path(File), FileName, - std::move(CommandLine), "")}; + if (RelPathPrefix.empty()) { + // Use the absolute path in the compile command. + CommandLine.push_back(File); + } else { + // Build a relative path using RelPathPrefix. + SmallString<32> RelativeFilePath(RelPathPrefix); + llvm::sys::path::append(RelativeFilePath, FileName); + CommandLine.push_back(RelativeFilePath.str()); + } + + return {tooling::CompileCommand( + Directory != StringRef() ? Directory : sys::path::parent_path(File), + FileName, std::move(CommandLine), "")}; } const char *testRoot() { diff --git a/clang-tools-extra/unittests/clangd/TestFS.h b/clang-tools-extra/unittests/clangd/TestFS.h index 9c2a15e63d3..a0efb755d00 100644 --- a/clang-tools-extra/unittests/clangd/TestFS.h +++ b/clang-tools-extra/unittests/clangd/TestFS.h @@ -40,15 +40,22 @@ public: // A Compilation database that returns a fixed set of compile flags. class MockCompilationDatabase : public GlobalCompilationDatabase { public: - /// When \p UseRelPaths is true, uses relative paths in compile commands. - /// When \p UseRelPaths is false, uses absoulte paths. - MockCompilationDatabase(bool UseRelPaths = false); + /// If \p Directory is not empty, use that as the Directory field of the + /// CompileCommand. + /// + /// If \p RelPathPrefix is not empty, use that as a prefix in front of the + /// source file name, instead of using an absolute path. + MockCompilationDatabase(StringRef Directory = StringRef(), + StringRef RelPathPrefix = StringRef()); llvm::Optional<tooling::CompileCommand> getCompileCommand(PathRef File) const override; std::vector<std::string> ExtraClangFlags; - const bool UseRelPaths; + +private: + StringRef Directory; + StringRef RelPathPrefix; }; // Returns an absolute (fake) test directory for this OS. diff --git a/clang-tools-extra/unittests/clangd/XRefsTests.cpp b/clang-tools-extra/unittests/clangd/XRefsTests.cpp index 3383dd54341..b08af32df21 100644 --- a/clang-tools-extra/unittests/clangd/XRefsTests.cpp +++ b/clang-tools-extra/unittests/clangd/XRefsTests.cpp @@ -312,27 +312,65 @@ TEST(GoToDefinition, All) { } TEST(GoToDefinition, RelPathsInCompileCommand) { + // The source is in "/clangd-test/src". + // We build in "/clangd-test/build". + Annotations SourceAnnotations(R"cpp( +#include "header_in_preamble.h" int [[foo]]; -int baz = f^oo; +#include "header_not_in_preamble.h" +int baz = f$p1^oo + bar_pre$p2^amble + bar_not_pre$p3^amble; +)cpp"); + + Annotations HeaderInPreambleAnnotations(R"cpp( +int [[bar_preamble]]; +)cpp"); + + Annotations HeaderNotInPreambleAnnotations(R"cpp( +int [[bar_not_preamble]]; )cpp"); + // Make the compilation paths appear as ../src/foo.cpp in the compile + // commands. + SmallString<32> RelPathPrefix(".."); + llvm::sys::path::append(RelPathPrefix, "src"); + std::string BuildDir = testPath("build"); + MockCompilationDatabase CDB(BuildDir, RelPathPrefix); + IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB(/*UseRelPaths=*/true); MockFSProvider FS; ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - auto FooCpp = testPath("foo.cpp"); + // Fill the filesystem. + auto FooCpp = testPath("src/foo.cpp"); FS.Files[FooCpp] = ""; + auto HeaderInPreambleH = testPath("src/header_in_preamble.h"); + FS.Files[HeaderInPreambleH] = HeaderInPreambleAnnotations.code(); + auto HeaderNotInPreambleH = testPath("src/header_not_in_preamble.h"); + FS.Files[HeaderNotInPreambleH] = HeaderNotInPreambleAnnotations.code(); - Server.addDocument(FooCpp, SourceAnnotations.code()); runAddDocument(Server, FooCpp, SourceAnnotations.code()); + + // Go to a definition in main source file. auto Locations = - runFindDefinitions(Server, FooCpp, SourceAnnotations.point()); + runFindDefinitions(Server, FooCpp, SourceAnnotations.point("p1")); EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp}, SourceAnnotations.range()})); + + // Go to a definition in header_in_preamble.h. + Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("p2")); + EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; + EXPECT_THAT(*Locations, + ElementsAre(Location{URIForFile{HeaderInPreambleH}, + HeaderInPreambleAnnotations.range()})); + + // Go to a definition in header_not_in_preamble.h. + Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("p3")); + EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; + EXPECT_THAT(*Locations, + ElementsAre(Location{URIForFile{HeaderNotInPreambleH}, + HeaderNotInPreambleAnnotations.range()})); } TEST(Hover, All) { |