|  | //===-- TestFS.cpp ----------------------------------------------*- C++ -*-===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | #include "TestFS.h" | 
|  | #include "GlobalCompilationDatabase.h" | 
|  | #include "URI.h" | 
|  | #include "support/Path.h" | 
|  | #include "llvm/ADT/None.h" | 
|  | #include "llvm/ADT/Optional.h" | 
|  | #include "llvm/ADT/StringRef.h" | 
|  | #include "llvm/Support/Errc.h" | 
|  | #include "llvm/Support/Path.h" | 
|  |  | 
|  | namespace clang { | 
|  | namespace clangd { | 
|  |  | 
|  | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> | 
|  | buildTestFS(llvm::StringMap<std::string> const &Files, | 
|  | llvm::StringMap<time_t> const &Timestamps) { | 
|  | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> MemFS( | 
|  | new llvm::vfs::InMemoryFileSystem); | 
|  | MemFS->setCurrentWorkingDirectory(testRoot()); | 
|  | for (auto &FileAndContents : Files) { | 
|  | llvm::StringRef File = FileAndContents.first(); | 
|  | MemFS->addFile( | 
|  | File, Timestamps.lookup(File), | 
|  | llvm::MemoryBuffer::getMemBufferCopy(FileAndContents.second, File)); | 
|  | } | 
|  | return MemFS; | 
|  | } | 
|  |  | 
|  | MockCompilationDatabase::MockCompilationDatabase(llvm::StringRef Directory, | 
|  | llvm::StringRef RelPathPrefix) | 
|  | : ExtraClangFlags({"-ffreestanding"}), Directory(Directory), | 
|  | RelPathPrefix(RelPathPrefix) { | 
|  | // -ffreestanding avoids implicit stdc-predef.h. | 
|  | } | 
|  |  | 
|  | llvm::Optional<ProjectInfo> | 
|  | MockCompilationDatabase::getProjectInfo(PathRef File) const { | 
|  | return ProjectInfo{std::string(Directory)}; | 
|  | } | 
|  |  | 
|  | llvm::Optional<tooling::CompileCommand> | 
|  | MockCompilationDatabase::getCompileCommand(PathRef File) const { | 
|  | if (ExtraClangFlags.empty()) | 
|  | return None; | 
|  |  | 
|  | auto FileName = llvm::sys::path::filename(File); | 
|  |  | 
|  | // Build the compile command. | 
|  | auto CommandLine = ExtraClangFlags; | 
|  | CommandLine.insert(CommandLine.begin(), "clang"); | 
|  | if (RelPathPrefix.empty()) { | 
|  | // Use the absolute path in the compile command. | 
|  | CommandLine.push_back(std::string(File)); | 
|  | } else { | 
|  | // Build a relative path using RelPathPrefix. | 
|  | llvm::SmallString<32> RelativeFilePath(RelPathPrefix); | 
|  | llvm::sys::path::append(RelativeFilePath, FileName); | 
|  | CommandLine.push_back(std::string(RelativeFilePath.str())); | 
|  | } | 
|  |  | 
|  | return {tooling::CompileCommand(Directory != llvm::StringRef() | 
|  | ? Directory | 
|  | : llvm::sys::path::parent_path(File), | 
|  | FileName, std::move(CommandLine), "")}; | 
|  | } | 
|  |  | 
|  | const char *testRoot() { | 
|  | #ifdef _WIN32 | 
|  | return "C:\\clangd-test"; | 
|  | #else | 
|  | return "/clangd-test"; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | std::string testPath(PathRef File, llvm::sys::path::Style Style) { | 
|  | assert(llvm::sys::path::is_relative(File) && "FileName should be relative"); | 
|  |  | 
|  | llvm::SmallString<32> NativeFile = File; | 
|  | llvm::sys::path::native(NativeFile, Style); | 
|  | llvm::SmallString<32> Path; | 
|  | llvm::sys::path::append(Path, Style, testRoot(), NativeFile); | 
|  | return std::string(Path.str()); | 
|  | } | 
|  |  | 
|  | /// unittest: is a scheme that refers to files relative to testRoot(). | 
|  | /// URI body is a path relative to testRoot() e.g. unittest:///x.h for | 
|  | /// /clangd-test/x.h. | 
|  | class TestScheme : public URIScheme { | 
|  | public: | 
|  | static const char *Scheme; | 
|  |  | 
|  | llvm::Expected<std::string> | 
|  | getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, | 
|  | llvm::StringRef HintPath) const override { | 
|  | if (!HintPath.empty() && !HintPath.startswith(testRoot())) | 
|  | return error("Hint path is not empty and doesn't start with {0}: {1}", | 
|  | testRoot(), HintPath); | 
|  | if (!Body.consume_front("/")) | 
|  | return error("Body of an unittest: URI must start with '/'"); | 
|  | llvm::SmallString<16> Path(Body.begin(), Body.end()); | 
|  | llvm::sys::path::native(Path); | 
|  | return testPath(Path); | 
|  | } | 
|  |  | 
|  | llvm::Expected<URI> | 
|  | uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { | 
|  | llvm::StringRef Body = AbsolutePath; | 
|  | if (!Body.consume_front(testRoot())) | 
|  | return error("{0} does not start with {1}", AbsolutePath, testRoot()); | 
|  |  | 
|  | return URI(Scheme, /*Authority=*/"", | 
|  | llvm::sys::path::convert_to_slash(Body)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | const char *TestScheme::Scheme = "unittest"; | 
|  |  | 
|  | static URISchemeRegistry::Add<TestScheme> X(TestScheme::Scheme, "Test schema"); | 
|  |  | 
|  | volatile int UnittestSchemeAnchorSource = 0; | 
|  |  | 
|  | } // namespace clangd | 
|  | } // namespace clang |