clang-tools  15.0.0git
LSPClient.cpp
Go to the documentation of this file.
1 //===-- LSPClient.cpp - Helper for ClangdLSPServer tests ------------------===//
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 
9 #include "LSPClient.h"
10 #include "Protocol.h"
11 #include "TestFS.h"
12 #include "Transport.h"
13 #include "support/Logger.h"
14 #include "support/Threading.h"
15 #include "llvm/Support/Path.h"
16 #include "llvm/Support/raw_ostream.h"
17 #include "gtest/gtest.h"
18 #include <condition_variable>
19 #include <queue>
20 
21 namespace clang {
22 namespace clangd {
23 
24 llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() {
25  std::unique_lock<std::mutex> Lock(Mu);
26  if (!clangd::wait(Lock, CV, timeoutSeconds(10),
27  [this] { return Value.hasValue(); })) {
28  ADD_FAILURE() << "No result from call after 10 seconds!";
29  return llvm::json::Value(nullptr);
30  }
31  auto Res = std::move(*Value);
32  Value.reset();
33  return Res;
34 }
35 
37  auto ExpValue = take();
38  if (!ExpValue) {
39  ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError());
40  return llvm::json::Value(nullptr);
41  }
42  return std::move(*ExpValue);
43 }
44 
45 void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) {
46  std::lock_guard<std::mutex> Lock(Mu);
47  if (Value) {
48  ADD_FAILURE() << "Multiple replies";
49  llvm::consumeError(V.takeError());
50  return;
51  }
52  Value = std::move(V);
53  CV.notify_all();
54 }
55 
57  if (Value && !*Value) {
58  ADD_FAILURE() << llvm::toString(Value->takeError());
59  }
60 }
61 
62 static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) {
63  // We invert <<< and >>> as the combined log is from the server's viewpoint.
64  vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V);
65 }
66 
68 public:
69  std::pair<llvm::json::Value, CallResult *> addCallSlot() {
70  std::lock_guard<std::mutex> Lock(Mu);
71  unsigned ID = CallResults.size();
72  CallResults.emplace_back();
73  return {ID, &CallResults.back()};
74  }
75 
76  // A null action causes the transport to shut down.
77  void enqueue(std::function<void(MessageHandler &)> Action) {
78  std::lock_guard<std::mutex> Lock(Mu);
79  Actions.push(std::move(Action));
80  CV.notify_all();
81  }
82 
83  std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) {
84  std::vector<llvm::json::Value> Result;
85  {
86  std::lock_guard<std::mutex> Lock(Mu);
87  std::swap(Result, Notifications[Method]);
88  }
89  return Result;
90  }
91 
92 private:
93  void reply(llvm::json::Value ID,
94  llvm::Expected<llvm::json::Value> V) override {
95  if (V) // Nothing additional to log for error.
96  logBody("reply", *V, /*Send=*/false);
97  std::lock_guard<std::mutex> Lock(Mu);
98  if (auto I = ID.getAsInteger()) {
99  if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) {
100  CallResults[*I].set(std::move(V));
101  return;
102  }
103  }
104  ADD_FAILURE() << "Invalid reply to ID " << ID;
105  llvm::consumeError(std::move(V).takeError());
106  }
107 
108  void notify(llvm::StringRef Method, llvm::json::Value V) override {
109  logBody(Method, V, /*Send=*/false);
110  std::lock_guard<std::mutex> Lock(Mu);
111  Notifications[Method].push_back(std::move(V));
112  }
113 
114  void call(llvm::StringRef Method, llvm::json::Value Params,
115  llvm::json::Value ID) override {
116  logBody(Method, Params, /*Send=*/false);
117  ADD_FAILURE() << "Unexpected server->client call " << Method;
118  }
119 
120  llvm::Error loop(MessageHandler &H) override {
121  std::unique_lock<std::mutex> Lock(Mu);
122  while (true) {
123  CV.wait(Lock, [&] { return !Actions.empty(); });
124  if (!Actions.front()) // Stop!
125  return llvm::Error::success();
126  auto Action = std::move(Actions.front());
127  Actions.pop();
128  Lock.unlock();
129  Action(H);
130  Lock.lock();
131  }
132  }
133 
134  std::mutex Mu;
135  std::deque<CallResult> CallResults;
136  std::queue<std::function<void(Transport::MessageHandler &)>> Actions;
137  std::condition_variable CV;
138  llvm::StringMap<std::vector<llvm::json::Value>> Notifications;
139 };
140 
141 LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {}
142 LSPClient::~LSPClient() = default;
143 
144 LSPClient::CallResult &LSPClient::call(llvm::StringRef Method,
145  llvm::json::Value Params) {
146  auto Slot = T->addCallSlot();
147  T->enqueue([ID(Slot.first), Method(Method.str()),
148  Params(std::move(Params))](Transport::MessageHandler &H) {
149  logBody(Method, Params, /*Send=*/true);
150  H.onCall(Method, std::move(Params), ID);
151  });
152  return *Slot.second;
153 }
154 
155 void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) {
156  T->enqueue([Method(Method.str()),
157  Params(std::move(Params))](Transport::MessageHandler &H) {
158  logBody(Method, Params, /*Send=*/true);
159  H.onNotify(Method, std::move(Params));
160  });
161 }
162 
163 std::vector<llvm::json::Value>
164 LSPClient::takeNotifications(llvm::StringRef Method) {
165  return T->takeNotifications(Method);
166 }
167 
168 void LSPClient::stop() { T->enqueue(nullptr); }
169 
171 
172 using Obj = llvm::json::Object;
173 
175  std::string Storage;
176  if (!llvm::sys::path::is_absolute(Path))
177  Path = Storage = testPath(Path);
179 }
181  return Obj{{"uri", uri(Path)}};
182 }
183 
184 void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) {
185  notify(
186  "textDocument/didOpen",
187  Obj{{"textDocument",
188  Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}});
189 }
190 void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) {
191  notify("textDocument/didChange",
192  Obj{{"textDocument", documentID(Path)},
193  {"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}});
194 }
195 void LSPClient::didClose(llvm::StringRef Path) {
196  notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}});
197 }
198 
199 void LSPClient::sync() { call("sync", nullptr).takeValue(); }
200 
201 llvm::Optional<std::vector<llvm::json::Value>>
202 LSPClient::diagnostics(llvm::StringRef Path) {
203  sync();
204  auto Notifications = takeNotifications("textDocument/publishDiagnostics");
205  for (const auto &Notification : llvm::reverse(Notifications)) {
206  if (const auto *PubDiagsParams = Notification.getAsObject()) {
207  auto U = PubDiagsParams->getString("uri");
208  auto *D = PubDiagsParams->getArray("diagnostics");
209  if (!U || !D) {
210  ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams;
211  continue;
212  }
213  if (*U == uri(Path))
214  return std::vector<llvm::json::Value>(D->begin(), D->end());
215  }
216  }
217  return {};
218 }
219 
220 } // namespace clangd
221 } // namespace clang
clang::clangd::LSPClient::didOpen
void didOpen(llvm::StringRef Path, llvm::StringRef Content)
Definition: LSPClient.cpp:184
clang::clangd::LSPClient::diagnostics
llvm::Optional< std::vector< llvm::json::Value > > diagnostics(llvm::StringRef Path)
Definition: LSPClient.cpp:202
clang::clangd::LSPClient::CallResult::takeValue
llvm::json::Value takeValue()
Definition: LSPClient.cpp:36
LSPClient.h
clang::clangd::timeoutSeconds
Deadline timeoutSeconds(llvm::Optional< double > Seconds)
Makes a deadline from a timeout in seconds. None means wait forever.
Definition: Threading.cpp:112
clang::clangd::LSPClient::TransportImpl::enqueue
void enqueue(std::function< void(MessageHandler &)> Action)
Definition: LSPClient.cpp:77
clang::clangd::LSPClient::CallResult::~CallResult
~CallResult()
Definition: LSPClient.cpp:56
clang::clangd::Obj
llvm::json::Object Obj
Definition: LSPClient.cpp:172
clang::clangd::testPath
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:94
clang::clangd::Path
std::string Path
A typedef to represent a file path.
Definition: Path.h:26
clang::clangd::LSPClient::TransportImpl
Definition: LSPClient.cpp:67
clang::clangd::LSPClient::TransportImpl::addCallSlot
std::pair< llvm::json::Value, CallResult * > addCallSlot()
Definition: LSPClient.cpp:69
clang::clangd::LSPClient::takeNotifications
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
Definition: LSPClient.cpp:164
clang::clangd::LSPClient::LSPClient
LSPClient()
Definition: LSPClient.cpp:141
clang::clangd::LSPClient::CallResult
Definition: LSPClient.h:32
Protocol.h
clang::clangd::LSPClient::~LSPClient
~LSPClient()
clang::clangd::wait
void wait(std::unique_lock< std::mutex > &Lock, std::condition_variable &CV, Deadline D)
Wait once on CV for the specified duration.
Definition: Threading.cpp:120
clang::clangd::Notification
A threadsafe flag that is initially clear.
Definition: Threading.h:90
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
clang::clangd::toJSON
llvm::json::Value toJSON(const FuzzyFindRequest &Request)
Definition: Index.cpp:45
Logger.h
TestFS.h
Threading.h
clang::clangd::LSPClient::notify
void notify(llvm::StringRef Method, llvm::json::Value Params)
Definition: LSPClient.cpp:155
clang::clangd::LSPClient::call
CallResult & call(llvm::StringRef Method, llvm::json::Value Params)
Definition: LSPClient.cpp:144
clang::clangd::LSPClient::didChange
void didChange(llvm::StringRef Path, llvm::StringRef Content)
Definition: LSPClient.cpp:190
clang::clangd::vlog
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:72
clang::clangd::CompletionItemKind::Method
@ Method
clang::clangd::Transport::MessageHandler
Definition: Transport.h:47
clang::clangd::Transport
Definition: Transport.h:35
clang::clangd::LSPClient::TransportImpl::takeNotifications
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
Definition: LSPClient.cpp:83
Transport.h
clang::clangd::LSPClient::didClose
void didClose(llvm::StringRef Path)
Definition: LSPClient.cpp:195
clang::clangd::logBody
static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send)
Definition: LSPClient.cpp:62
clang::clangd::LSPClient::transport
Transport & transport()
Definition: LSPClient.cpp:170
ID
static char ID
Definition: Logger.cpp:74
clang::clangd::LSPClient::documentID
static llvm::json::Value documentID(llvm::StringRef Path)
Definition: LSPClient.cpp:180
clang::clangd::LSPClient::sync
void sync()
Definition: LSPClient.cpp:199
clang::clangd::URIForFile::canonicalize
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition: Protocol.cpp:45
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::cppcoreguidelines::toString
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Definition: SpecialMemberFunctionsCheck.cpp:55
clang::clangd::LSPClient::CallResult::take
llvm::Expected< llvm::json::Value > take()
Definition: LSPClient.cpp:24
clang::clangd::LSPClient::stop
void stop()
Definition: LSPClient.cpp:168
clang::clangd::LSPClient::uri
static llvm::json::Value uri(llvm::StringRef Path)
Definition: LSPClient.cpp:174
Value
static constexpr bool Value
Definition: SuspiciousCallArgumentCheck.cpp:72
Action
FieldAction Action
Definition: MemberwiseConstructor.cpp:261