Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -10,6 +10,7 @@ #include "ClangdLSPServer.h" #include "JSONRPCDispatcher.h" #include "SourceCode.h" +#include "URI.h" #include "llvm/Support/FormatVariadic.h" using namespace clang::clangd; @@ -157,7 +158,7 @@ std::vector Edits = replacementsToEdits(*Code, *Replacements); WorkspaceEdit WE; - WE.changes = {{Params.textDocument.uri.uri, Edits}}; + WE.changes = {{Params.textDocument.uri.uri.toString(), Edits}}; reply(C, WE); } @@ -227,7 +228,7 @@ auto Edits = getFixIts(Params.textDocument.uri.file, D); if (!Edits.empty()) { WorkspaceEdit WE; - WE.changes = {{Params.textDocument.uri.uri, std::move(Edits)}}; + WE.changes = {{Params.textDocument.uri.uri.toString(), std::move(Edits)}}; Commands.push_back(json::obj{ {"title", llvm::formatv("Apply FixIt {0}", D.message)}, {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}, @@ -279,8 +280,16 @@ void ClangdLSPServer::onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) { llvm::Optional Result = Server.switchSourceHeader(Params.uri.file); - std::string ResultUri; - reply(C, Result ? URI::fromFile(*Result).uri : ""); + std::string ResultUri = ""; + if (Result) { + auto U = URI::create(*Result); + if (!U) + log(Context::empty(), "Failed to create an URI for file " + *Result + + ": " + llvm::toString(U.takeError())); + else + ResultUri = U->toString(); + } + reply(C, ResultUri); } void ClangdLSPServer::onDocumentHighlight(Ctx C, @@ -372,12 +381,20 @@ } // Publish diagnostics. + auto U = URI::create(File); + if (!U) + llvm_unreachable( + "onDiagnosticsReady: Expect creating URI for file to always work."); + URIWithFile Uri; + Uri.uri =std::move(*U); + Uri.file = File; + Out.writeMessage(json::obj{ {"jsonrpc", "2.0"}, {"method", "textDocument/publishDiagnostics"}, {"params", json::obj{ - {"uri", URI::fromFile(File)}, + {"uri", std::move(Uri)}, {"diagnostics", std::move(DiagnosticsJSON)}, }}, }); Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -25,6 +25,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H #include "JSONExpr.h" +#include "URI.h" #include "llvm/ADT/Optional.h" #include #include @@ -47,32 +48,28 @@ RequestCancelled = -32800, }; -struct URI { - std::string uri; +struct URIWithFile { + URI uri; std::string file; - static URI fromUri(llvm::StringRef uri); - static URI fromFile(llvm::StringRef file); - - friend bool operator==(const URI &LHS, const URI &RHS) { - return LHS.uri == RHS.uri; + friend bool operator==(const URIWithFile &LHS, const URIWithFile &RHS) { + return LHS.uri == RHS.uri && LHS.file == RHS.file; } - friend bool operator!=(const URI &LHS, const URI &RHS) { + friend bool operator!=(const URIWithFile &LHS, const URIWithFile &RHS) { return !(LHS == RHS); } - friend bool operator<(const URI &LHS, const URI &RHS) { - return LHS.uri < RHS.uri; + friend bool operator<(const URIWithFile &LHS, const URIWithFile &RHS) { + return LHS.uri < RHS.uri && LHS.file < RHS.file; } }; -json::Expr toJSON(const URI &U); -bool fromJSON(const json::Expr &, URI &); -llvm::raw_ostream &operator<<(llvm::raw_ostream &, const URI &); +json::Expr toJSON(const URIWithFile &U); +bool fromJSON(const json::Expr &, URIWithFile &); struct TextDocumentIdentifier { /// The text document's URI. - URI uri; + URIWithFile uri; }; bool fromJSON(const json::Expr &, TextDocumentIdentifier &); @@ -116,7 +113,7 @@ struct Location { /// The text document's URI. - URI uri; + URIWithFile uri; Range range; friend bool operator==(const Location &LHS, const Location &RHS) { @@ -153,7 +150,7 @@ struct TextDocumentItem { /// The text document's URI. - URI uri; + URIWithFile uri; /// The text document's language identifier. std::string languageId; @@ -194,7 +191,7 @@ /// The rootUri of the workspace. Is null if no /// folder is open. If both `rootPath` and `rootUri` are set /// `rootUri` wins. - llvm::Optional rootUri; + llvm::Optional rootUri; // User provided initialization options. // initializationOptions?: any; @@ -252,7 +249,7 @@ struct FileEvent { /// The file's URI. - URI uri; + URIWithFile uri; /// The change type. FileChangeType type; }; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -12,6 +12,8 @@ //===----------------------------------------------------------------------===// #include "Protocol.h" +#include "URI.h" +#include "Logger.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" @@ -22,45 +24,31 @@ namespace clang { namespace clangd { -URI URI::fromUri(llvm::StringRef uri) { - URI Result; - Result.uri = uri; - uri.consume_front("file://"); - // Also trim authority-less URIs - uri.consume_front("file:"); - // For Windows paths e.g. /X: - if (uri.size() > 2 && uri[0] == '/' && uri[2] == ':') - uri.consume_front("/"); - // Make sure that file paths are in native separators - Result.file = llvm::sys::path::convert_to_slash(uri); - return Result; -} - -URI URI::fromFile(llvm::StringRef file) { - using namespace llvm::sys; - URI Result; - Result.file = file; - Result.uri = "file://"; - // For Windows paths e.g. X: - if (file.size() > 1 && file[1] == ':') - Result.uri += "/"; - // Make sure that uri paths are with posix separators - Result.uri += path::convert_to_slash(file, path::Style::posix); - return Result; -} - -bool fromJSON(const json::Expr &E, URI &R) { +bool fromJSON(const json::Expr &E, URIWithFile &R) { if (auto S = E.asString()) { - R = URI::fromUri(*S); + auto U = URI::parse(*S); + if (!U) { + log(Context::empty(), + "Failed to parse URI " + *S + ": " + llvm::toString(U.takeError())); + return false; + } + auto Resolved = URI::resolve(*U); + if (!Resolved) { + log(Context::empty(), "Failed to resolve file path for URI " + *S + ": " + + llvm::toString(Resolved.takeError())); + return false; + } + R.file = std::move(*Resolved); + R.uri = std::move(*U); return true; } return false; } -json::Expr toJSON(const URI &U) { return U.uri; } +json::Expr toJSON(const URIWithFile &U) { return U.uri.toString(); } -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URI &U) { - return OS << U.uri; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIWithFile &U) { + return OS << U.uri.toString(); } bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) { Index: clangd/URI.h =================================================================== --- clangd/URI.h +++ clangd/URI.h @@ -26,8 +26,11 @@ /// Clangd handles URIs of the form :[//]. It doesn't /// further split the authority or body into constituent parts (e.g. query /// strings is included in the body). -class FileURI { +class URI { public: + // By default, create a simplest valid file URI. + URI() : Scheme("file") {} + /// Returns decoded scheme e.g. "https" llvm::StringRef scheme() const { return Scheme; } /// Returns decoded authority e.g. "reviews.lvm.org" @@ -38,36 +41,39 @@ /// Returns a string URI with all components percent-encoded. std::string toString() const; - /// Create a FileURI from unescaped scheme+authority+body. - static llvm::Expected create(llvm::StringRef Scheme, - llvm::StringRef Authority, - llvm::StringRef Body); + /// Create a URI from unescaped scheme+authority+body. + static llvm::Expected create(llvm::StringRef Scheme, + llvm::StringRef Authority, + llvm::StringRef Body); - /// Creates a FileURI for a file in the given scheme. \p Scheme must be + /// Creates a URI for a file in the given scheme. \p Scheme must be /// registered. The URI is percent-encoded. - static llvm::Expected create(llvm::StringRef AbsolutePath, - llvm::StringRef Scheme = "file"); + static llvm::Expected create(llvm::StringRef AbsolutePath, + llvm::StringRef Scheme = "file"); /// Parse a URI string ":[///]". Percent-encoded /// characters in the URI will be decoded. - static llvm::Expected parse(llvm::StringRef Uri); + static llvm::Expected parse(llvm::StringRef Uri); /// Resolves the absolute path of \p U. If there is no matching scheme, or the /// URI is invalid in the scheme, this returns an error. /// /// \p HintPath A related path, such as the current file or working directory, /// which can help disambiguate when the same file exists in many workspaces. - static llvm::Expected resolve(const FileURI &U, + static llvm::Expected resolve(const URI &U, llvm::StringRef HintPath = ""); - friend bool operator==(const FileURI &LHS, const FileURI &RHS) { + friend bool operator==(const URI &LHS, const URI &RHS) { return std::tie(LHS.Scheme, LHS.Authority, LHS.Body) == std::tie(RHS.Scheme, RHS.Authority, RHS.Body); } -private: - FileURI() = default; + friend bool operator<(const URI &LHS, const URI &RHS) { + return std::tie(LHS.Scheme, LHS.Authority, LHS.Body) < + std::tie(RHS.Scheme, RHS.Authority, RHS.Body); + } +private: std::string Scheme; std::string Authority; std::string Body; @@ -81,13 +87,13 @@ virtual ~URIScheme() = default; /// Returns the absolute path of the file corresponding to the URI - /// authority+body in the file system. See FileURI::resolve for semantics of + /// authority+body in the file system. See URI::resolve for semantics of /// \p HintPath. virtual llvm::Expected getAbsolutePath(llvm::StringRef Authority, llvm::StringRef Body, llvm::StringRef HintPath) const = 0; - virtual llvm::Expected + virtual llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const = 0; }; Index: clangd/URI.cpp =================================================================== --- clangd/URI.cpp +++ clangd/URI.cpp @@ -47,7 +47,7 @@ return std::string(Path.begin(), Path.end()); } - llvm::Expected + llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { using namespace llvm::sys; @@ -56,7 +56,7 @@ if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':') Body = "/"; Body += path::convert_to_slash(AbsolutePath); - return FileURI::create(Scheme, /*Authority=*/"", Body); + return URI::create(Scheme, /*Authority=*/"", Body); } }; @@ -129,22 +129,22 @@ } // namespace -llvm::Expected FileURI::create(llvm::StringRef Scheme, - llvm::StringRef Authority, - llvm::StringRef Body) { +llvm::Expected URI::create(llvm::StringRef Scheme, + llvm::StringRef Authority, + llvm::StringRef Body) { if (Scheme.empty()) return make_string_error("Scheme must be specified in a URI."); if (!Authority.empty() && !Body.startswith("/")) return make_string_error( "URI body must start with '/' when authority is present."); - FileURI U; + URI U; U.Scheme = Scheme; U.Authority = Authority; U.Body = Body; return U; } -std::string FileURI::toString() const { +std::string URI::toString() const { std::string Result; llvm::raw_string_ostream OS(Result); OS << percentEncode(Scheme) << ":"; @@ -159,8 +159,8 @@ return Result; } -llvm::Expected FileURI::parse(llvm::StringRef OrigUri) { - FileURI U; +llvm::Expected URI::parse(llvm::StringRef OrigUri) { + URI U; llvm::StringRef Uri = OrigUri; auto Pos = Uri.find(':'); @@ -177,8 +177,8 @@ return U; } -llvm::Expected FileURI::create(llvm::StringRef AbsolutePath, - llvm::StringRef Scheme) { +llvm::Expected URI::create(llvm::StringRef AbsolutePath, + llvm::StringRef Scheme) { if (!llvm::sys::path::is_absolute(AbsolutePath)) return make_string_error("Not a valid absolute path: " + AbsolutePath); auto S = findSchemeByName(Scheme); @@ -187,8 +187,8 @@ return S->get()->uriFromAbsolutePath(AbsolutePath); } -llvm::Expected FileURI::resolve(const FileURI &Uri, - llvm::StringRef HintPath) { +llvm::Expected URI::resolve(const URI &Uri, + llvm::StringRef HintPath) { auto S = findSchemeByName(Uri.Scheme); if (!S) return S.takeError(); Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -7,6 +7,8 @@ // //===---------------------------------------------------------------------===// #include "XRefs.h" +#include "Logger.h" +#include "URI.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" namespace clang { @@ -139,7 +141,14 @@ StringRef FilePath = F->tryGetRealPathName(); if (FilePath.empty()) FilePath = F->getName(); - L.uri = URI::fromFile(FilePath); + auto U = URI::create(FilePath); + if (!U) { + log(Context::empty(), "Failed to create an URI for " + FilePath + ": " + + llvm::toString(U.takeError())); + return llvm::None; + } + L.uri.file = FilePath; + L.uri.uri = std::move(*U); L.range = R; return L; } Index: unittests/clangd/URITests.cpp =================================================================== --- unittests/clangd/URITests.cpp +++ unittests/clangd/URITests.cpp @@ -40,11 +40,11 @@ .str(); } - llvm::Expected + llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { auto Pos = AbsolutePath.find(TestRoot); assert(Pos != llvm::StringRef::npos); - return FileURI::create( + return URI::create( Scheme, /*Authority=*/"", AbsolutePath.substr(Pos + llvm::StringRef(TestRoot).size())); } @@ -57,7 +57,7 @@ std::string createOrDie(llvm::StringRef AbsolutePath, llvm::StringRef Scheme = "file") { - auto Uri = FileURI::create(AbsolutePath, Scheme); + auto Uri = URI::create(AbsolutePath, Scheme); if (!Uri) llvm_unreachable(llvm::toString(Uri.takeError()).c_str()); return Uri->toString(); @@ -65,14 +65,14 @@ std::string createOrDie(llvm::StringRef Scheme, llvm::StringRef Authority, llvm::StringRef Body) { - auto Uri = FileURI::create(Scheme, Authority, Body); + auto Uri = URI::create(Scheme, Authority, Body); if (!Uri) llvm_unreachable(llvm::toString(Uri.takeError()).c_str()); return Uri->toString(); } -FileURI parseOrDie(llvm::StringRef Uri) { - auto U = FileURI::parse(Uri); +URI parseOrDie(llvm::StringRef Uri) { + auto U = URI::parse(Uri); if (!U) llvm_unreachable(llvm::toString(U.takeError()).c_str()); return *U; @@ -93,8 +93,8 @@ EXPECT_EQ(parseOrDie("x:a%21b%3ac~").body(), "a!b:c~"); } -std::string resolveOrDie(const FileURI &U, llvm::StringRef HintPath = "") { - auto Path = FileURI::resolve(U, HintPath); +std::string resolveOrDie(const URI &U, llvm::StringRef HintPath = "") { + auto Path = URI::resolve(U, HintPath); if (!Path) llvm_unreachable(llvm::toString(Path.takeError()).c_str()); return *Path; @@ -110,7 +110,7 @@ } TEST(URITest, FailedCreate) { - auto Fail = [](llvm::Expected U) { + auto Fail = [](llvm::Expected U) { if (!U) { llvm::consumeError(U.takeError()); return true; @@ -120,15 +120,15 @@ // Create from scheme+authority+body: // // Scheme must be provided. - EXPECT_TRUE(Fail(FileURI::create("", "auth", "/a"))); + EXPECT_TRUE(Fail(URI::create("", "auth", "/a"))); // Body must start with '/' if authority is present. - EXPECT_TRUE(Fail(FileURI::create("scheme", "auth", "x/y/z"))); + EXPECT_TRUE(Fail(URI::create("scheme", "auth", "x/y/z"))); // Create from scheme registry: // - EXPECT_TRUE(Fail(FileURI::create("/x/y/z", "no"))); + EXPECT_TRUE(Fail(URI::create("/x/y/z", "no"))); // Path has to be absolute. - EXPECT_TRUE(Fail(FileURI::create("x/y/z"))); + EXPECT_TRUE(Fail(URI::create("x/y/z"))); } TEST(URITest, Parse) { @@ -163,7 +163,7 @@ TEST(URITest, ParseFailed) { auto FailedParse = [](llvm::StringRef U) { - auto URI = FileURI::parse(U); + auto URI = URI::parse(U); if (!URI) { llvm::consumeError(URI.takeError()); return true; @@ -194,14 +194,14 @@ TEST(URITest, Platform) { auto Path = getVirtualTestFilePath("x"); - auto U = FileURI::create(Path, "file"); + auto U = URI::create(Path, "file"); EXPECT_TRUE(static_cast(U)); EXPECT_THAT(resolveOrDie(*U), Path.str()); } TEST(URITest, ResolveFailed) { auto FailedResolve = [](llvm::StringRef Uri) { - auto Path = FileURI::resolve(parseOrDie(Uri)); + auto Path = URI::resolve(parseOrDie(Uri)); if (!Path) { llvm::consumeError(Path.takeError()); return true; Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "ClangdUnit.h" #include "Matchers.h" +#include "TestFS.h" #include "XRefs.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PCHContainerOperations.h" @@ -38,7 +39,9 @@ // FIXME: this is duplicated with FileIndexTests. Share it. ParsedAST build(StringRef Code) { - auto CI = createInvocationFromCommandLine({"clang", "-xc++", "Foo.cpp"}); + auto TestFile = getVirtualTestFilePath("Foo.cpp"); + auto CI = + createInvocationFromCommandLine({"clang", "-xc++", TestFile.c_str()}); auto Buf = MemoryBuffer::getMemBuffer(Code); auto AST = ParsedAST::Build( Context::empty(), std::move(CI), nullptr, std::move(Buf),