xref: /llvm-project/clang-tools-extra/clangd/PathMapping.cpp (revision d5953e3e3092f7142a07aa012fc9665ede09e53b)
1 //===--- PathMapping.cpp - apply path mappings to LSP messages -===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "PathMapping.h"
9 #include "Transport.h"
10 #include "URI.h"
11 #include "support/Logger.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/Path.h"
14 #include <algorithm>
15 #include <optional>
16 #include <tuple>
17 
18 namespace clang {
19 namespace clangd {
doPathMapping(llvm::StringRef S,PathMapping::Direction Dir,const PathMappings & Mappings)20 std::optional<std::string> doPathMapping(llvm::StringRef S,
21                                          PathMapping::Direction Dir,
22                                          const PathMappings &Mappings) {
23   // Return early to optimize for the common case, wherein S is not a file URI
24   if (!S.starts_with("file://"))
25     return std::nullopt;
26   auto Uri = URI::parse(S);
27   if (!Uri) {
28     llvm::consumeError(Uri.takeError());
29     return std::nullopt;
30   }
31   for (const auto &Mapping : Mappings) {
32     const std::string &From = Dir == PathMapping::Direction::ClientToServer
33                                   ? Mapping.ClientPath
34                                   : Mapping.ServerPath;
35     const std::string &To = Dir == PathMapping::Direction::ClientToServer
36                                 ? Mapping.ServerPath
37                                 : Mapping.ClientPath;
38     llvm::StringRef Body = Uri->body();
39     if (Body.consume_front(From) && (Body.empty() || Body.front() == '/')) {
40       std::string MappedBody = (To + Body).str();
41       return URI(Uri->scheme(), Uri->authority(), MappedBody)
42           .toString();
43     }
44   }
45   return std::nullopt;
46 }
47 
applyPathMappings(llvm::json::Value & V,PathMapping::Direction Dir,const PathMappings & Mappings)48 void applyPathMappings(llvm::json::Value &V, PathMapping::Direction Dir,
49                        const PathMappings &Mappings) {
50   using Kind = llvm::json::Value::Kind;
51   Kind K = V.kind();
52   if (K == Kind::Object) {
53     llvm::json::Object *Obj = V.getAsObject();
54     llvm::json::Object MappedObj;
55     // 1. Map all the Keys
56     for (auto &KV : *Obj) {
57       if (std::optional<std::string> MappedKey =
58               doPathMapping(KV.first.str(), Dir, Mappings)) {
59         MappedObj.try_emplace(std::move(*MappedKey), std::move(KV.second));
60       } else {
61         MappedObj.try_emplace(std::move(KV.first), std::move(KV.second));
62       }
63     }
64     *Obj = std::move(MappedObj);
65     // 2. Map all the values
66     for (auto &KV : *Obj)
67       applyPathMappings(KV.second, Dir, Mappings);
68   } else if (K == Kind::Array) {
69     for (llvm::json::Value &Val : *V.getAsArray())
70       applyPathMappings(Val, Dir, Mappings);
71   } else if (K == Kind::String) {
72     if (std::optional<std::string> Mapped =
73             doPathMapping(*V.getAsString(), Dir, Mappings))
74       V = std::move(*Mapped);
75   }
76 }
77 
78 namespace {
79 
80 class PathMappingMessageHandler : public Transport::MessageHandler {
81 public:
PathMappingMessageHandler(MessageHandler & Handler,const PathMappings & Mappings)82   PathMappingMessageHandler(MessageHandler &Handler,
83                             const PathMappings &Mappings)
84       : WrappedHandler(Handler), Mappings(Mappings) {}
85 
onNotify(llvm::StringRef Method,llvm::json::Value Params)86   bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
87     applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
88     return WrappedHandler.onNotify(Method, std::move(Params));
89   }
90 
onCall(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)91   bool onCall(llvm::StringRef Method, llvm::json::Value Params,
92               llvm::json::Value ID) override {
93     applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
94     return WrappedHandler.onCall(Method, std::move(Params), std::move(ID));
95   }
96 
onReply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> Result)97   bool onReply(llvm::json::Value ID,
98                llvm::Expected<llvm::json::Value> Result) override {
99     if (Result)
100       applyPathMappings(*Result, PathMapping::Direction::ClientToServer,
101                         Mappings);
102     return WrappedHandler.onReply(std::move(ID), std::move(Result));
103   }
104 
105 private:
106   Transport::MessageHandler &WrappedHandler;
107   const PathMappings &Mappings;
108 };
109 
110 // Apply path mappings to all LSP messages by intercepting all params/results
111 // and then delegating to the normal transport
112 class PathMappingTransport : public Transport {
113 public:
PathMappingTransport(std::unique_ptr<Transport> Transp,PathMappings Mappings)114   PathMappingTransport(std::unique_ptr<Transport> Transp, PathMappings Mappings)
115       : WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {}
116 
notify(llvm::StringRef Method,llvm::json::Value Params)117   void notify(llvm::StringRef Method, llvm::json::Value Params) override {
118     applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
119     WrappedTransport->notify(Method, std::move(Params));
120   }
121 
call(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)122   void call(llvm::StringRef Method, llvm::json::Value Params,
123             llvm::json::Value ID) override {
124     applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
125     WrappedTransport->call(Method, std::move(Params), std::move(ID));
126   }
127 
reply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> Result)128   void reply(llvm::json::Value ID,
129              llvm::Expected<llvm::json::Value> Result) override {
130     if (Result)
131       applyPathMappings(*Result, PathMapping::Direction::ServerToClient,
132                         Mappings);
133     WrappedTransport->reply(std::move(ID), std::move(Result));
134   }
135 
loop(MessageHandler & Handler)136   llvm::Error loop(MessageHandler &Handler) override {
137     PathMappingMessageHandler WrappedHandler(Handler, Mappings);
138     return WrappedTransport->loop(WrappedHandler);
139   }
140 
141 private:
142   std::unique_ptr<Transport> WrappedTransport;
143   PathMappings Mappings;
144 };
145 
146 // Converts a unix/windows path to the path portion of a file URI
147 // e.g. "C:\foo" -> "/C:/foo"
parsePath(llvm::StringRef Path)148 llvm::Expected<std::string> parsePath(llvm::StringRef Path) {
149   namespace path = llvm::sys::path;
150   if (path::is_absolute(Path, path::Style::posix)) {
151     return std::string(Path);
152   }
153   if (path::is_absolute(Path, path::Style::windows)) {
154     std::string Converted = path::convert_to_slash(Path, path::Style::windows);
155     if (Converted.front() != '/')
156       Converted = "/" + Converted;
157     return Converted;
158   }
159   return error("Path not absolute: {0}", Path);
160 }
161 
162 } // namespace
163 
operator <<(llvm::raw_ostream & OS,const PathMapping & M)164 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PathMapping &M) {
165   return OS << M.ClientPath << "=" << M.ServerPath;
166 }
167 
168 llvm::Expected<PathMappings>
parsePathMappings(llvm::StringRef RawPathMappings)169 parsePathMappings(llvm::StringRef RawPathMappings) {
170   llvm::StringRef ClientPath, ServerPath, PathPair, Rest = RawPathMappings;
171   PathMappings ParsedMappings;
172   while (!Rest.empty()) {
173     std::tie(PathPair, Rest) = Rest.split(",");
174     std::tie(ClientPath, ServerPath) = PathPair.split("=");
175     if (ClientPath.empty() || ServerPath.empty())
176       return error("Not a valid path mapping pair: {0}", PathPair);
177     llvm::Expected<std::string> ParsedClientPath = parsePath(ClientPath);
178     if (!ParsedClientPath)
179       return ParsedClientPath.takeError();
180     llvm::Expected<std::string> ParsedServerPath = parsePath(ServerPath);
181     if (!ParsedServerPath)
182       return ParsedServerPath.takeError();
183     ParsedMappings.push_back(
184         {std::move(*ParsedClientPath), std::move(*ParsedServerPath)});
185   }
186   return ParsedMappings;
187 }
188 
189 std::unique_ptr<Transport>
createPathMappingTransport(std::unique_ptr<Transport> Transp,PathMappings Mappings)190 createPathMappingTransport(std::unique_ptr<Transport> Transp,
191                            PathMappings Mappings) {
192   return std::make_unique<PathMappingTransport>(std::move(Transp), Mappings);
193 }
194 
195 } // namespace clangd
196 } // namespace clang
197