v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
message-handler.cc
Go to the documentation of this file.
1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
7
15
16EXPORT_CONTEXTUAL_VARIABLE(v8::internal::torque::DiagnosticsFiles)
17
18namespace v8 {
19namespace internal {
20namespace torque {
21
22namespace ls {
23
24static const char kContentLength[] = "Content-Length: ";
25static const size_t kContentLengthSize = sizeof(kContentLength) - 1;
26
27#ifdef V8_OS_WIN
28// On Windows, in text mode, \n is translated to \r\n.
29constexpr const char* kProtocolLineEnding = "\n\n";
30#else
31constexpr const char* kProtocolLineEnding = "\r\n\r\n";
32#endif
33
35 std::string line;
36 std::getline(std::cin, line);
37
38 if (line.rfind(kContentLength) != 0) {
39 // Invalid message, we just crash.
40 Logger::Log("[fatal] Did not find Content-Length ...\n");
42 }
43
44 const int content_length = std::atoi(line.substr(kContentLengthSize).c_str());
45 std::getline(std::cin, line);
46 std::string content(content_length, ' ');
47 std::cin.read(&content[0], content_length);
48
49 Logger::Log("[incoming] ", content, "\n\n");
50
51 return ParseJson(content).value;
52}
53
54void WriteMessage(JsonValue message) {
55 std::string content = SerializeToString(message);
56
57 Logger::Log("[outgoing] ", content, "\n\n");
58
59 std::cout << kContentLength << content.size() << kProtocolLineEnding;
60 std::cout << content << std::flush;
61}
62
63namespace {
64
65void ResetCompilationErrorDiagnostics(MessageWriter writer) {
66 for (const SourceId& source : DiagnosticsFiles::Get()) {
68 notification.set_method("textDocument/publishDiagnostics");
69
70 std::string error_file = SourceFileMap::AbsolutePath(source);
71 notification.params().set_uri(error_file);
72 // Trigger empty array creation.
73 USE(notification.params().diagnostics_size());
74
75 writer(std::move(notification.GetJsonValue()));
76 }
77 DiagnosticsFiles::Get() = {};
78}
79
80// Each notification must contain all diagnostics for a specific file,
81// because sending multiple notifications per file resets previously sent
82// diagnostics. Thus, two steps are needed:
83// 1) collect all notifications in this class.
84// 2) send one notification per entry (per file).
85class DiagnosticCollector {
86 public:
87 void AddTorqueMessage(const TorqueMessage& message) {
88 if (!ShouldAddMessageOfKind(message.kind)) return;
89
90 SourceId id =
91 message.position ? message.position->source : SourceId::Invalid();
92 auto& notification = GetOrCreateNotificationForSource(id);
93
94 Diagnostic diagnostic = notification.params().add_diagnostics();
95 diagnostic.set_severity(ServerityFor(message.kind));
96 diagnostic.set_message(message.message);
97 diagnostic.set_source("Torque Compiler");
98
99 if (message.position) {
100 PopulateRangeFromSourcePosition(diagnostic.range(), *message.position);
101 }
102 }
103
104 std::map<SourceId, PublishDiagnosticsNotification>& notifications() {
105 return notifications_;
106 }
107
108 private:
109 PublishDiagnosticsNotification& GetOrCreateNotificationForSource(
110 SourceId id) {
111 auto iter = notifications_.find(id);
112 if (iter != notifications_.end()) return iter->second;
113
115 notification.set_method("textDocument/publishDiagnostics");
116
117 std::string file =
118 id.IsValid() ? SourceFileMap::AbsolutePath(id) : "<unknown>";
119 notification.params().set_uri(file);
120 return notification;
121 }
122
123 bool ShouldAddMessageOfKind(TorqueMessage::Kind kind) {
124 // An error can easily cause a lot of false positive lint messages, due to
125 // unused variables, macros, etc. Thus we suppress subsequent lint messages
126 // when there are errors.
127 switch (kind) {
130 return true;
132 if (suppress_lint_messages_) return false;
133 return true;
134 }
135 }
136
137 void PopulateRangeFromSourcePosition(Range range,
138 const SourcePosition& position) {
139 range.start().set_line(position.start.line);
140 range.start().set_character(position.start.column);
141 range.end().set_line(position.end.line);
142 range.end().set_character(position.end.column);
143 }
144
146 switch (kind) {
148 return Diagnostic::kError;
151 }
152 }
153
154 std::map<SourceId, PublishDiagnosticsNotification> notifications_;
156};
157
158void SendCompilationDiagnostics(const TorqueCompilerResult& result,
159 MessageWriter writer) {
160 DiagnosticCollector collector;
161
162 // TODO(szuend): Split up messages by SourceId and sort them by line number.
163 for (const TorqueMessage& message : result.messages) {
164 collector.AddTorqueMessage(message);
165 }
166
167 for (auto& pair : collector.notifications()) {
168 PublishDiagnosticsNotification& notification = pair.second;
169 writer(std::move(notification.GetJsonValue()));
170
171 // Record all source files for which notifications are sent, so they
172 // can be reset before the next compiler run.
173 const SourceId& source = pair.first;
174 if (source.IsValid()) DiagnosticsFiles::Get().push_back(source);
175 }
176}
177
178} // namespace
179
181 LanguageServerData::Get() = std::move(result.language_server_data);
182 SourceFileMap::Get() = *result.source_file_map;
183
184 SendCompilationDiagnostics(result, writer);
185}
186
187namespace {
188
189void RecompileTorque(MessageWriter writer) {
190 Logger::Log("[info] Start compilation run ...\n");
191
193 options.output_directory = "";
194 options.collect_language_server_data = true;
195 options.force_assert_statements = true;
196
197 TorqueCompilerResult result = CompileTorque(TorqueFileList::Get(), options);
198
199 Logger::Log("[info] Finished compilation run ...\n");
200
201 CompilationFinished(std::move(result), writer);
202}
203
204void RecompileTorqueWithDiagnostics(MessageWriter writer) {
205 ResetCompilationErrorDiagnostics(writer);
206 RecompileTorque(writer);
207}
208
209void HandleInitializeRequest(InitializeRequest request, MessageWriter writer) {
210 InitializeResponse response;
211 response.set_id(request.id());
212 response.result().capabilities().textDocumentSync();
213 response.result().capabilities().set_definitionProvider(true);
214 response.result().capabilities().set_documentSymbolProvider(true);
215
216 // TODO(szuend): Register for document synchronisation here,
217 // so we work with the content that the client
218 // provides, not directly read from files.
219 // TODO(szuend): Check that the client actually supports dynamic
220 // "workspace/didChangeWatchedFiles" capability.
221 // TODO(szuend): Check if client supports "LocationLink". This will
222 // influence the result of "goto definition".
223 writer(std::move(response.GetJsonValue()));
224}
225
226void HandleInitializedNotification(MessageWriter writer) {
227 RegistrationRequest request;
228 // TODO(szuend): The language server needs a "global" request id counter.
229 request.set_id(2000);
230 request.set_method("client/registerCapability");
231
232 Registration reg = request.params().add_registrations();
233 auto options =
234 reg.registerOptions<DidChangeWatchedFilesRegistrationOptions>();
235 FileSystemWatcher watcher = options.add_watchers();
236 watcher.set_globPattern("**/*.tq");
237 watcher.set_kind(FileSystemWatcher::WatchKind::kAll);
238
239 reg.set_id("did-change-id");
240 reg.set_method("workspace/didChangeWatchedFiles");
241
242 writer(std::move(request.GetJsonValue()));
243}
244
245void HandleTorqueFileListNotification(TorqueFileListNotification notification,
246 MessageWriter writer) {
247 CHECK_EQ(notification.params().object()["files"].tag, JsonValue::ARRAY);
248
249 std::vector<std::string>& files = TorqueFileList::Get();
250 Logger::Log("[info] Initial file list:\n");
251 for (const auto& file_json :
252 notification.params().object()["files"].ToArray()) {
253 CHECK(file_json.IsString());
254
255 // We only consider file URIs (there shouldn't be anything else).
256 // Internally we store the URI instead of the path, eliminating the need
257 // to encode it again.
258 files.push_back(file_json.ToString());
259 Logger::Log(" ", file_json.ToString(), "\n");
260 }
261 RecompileTorqueWithDiagnostics(writer);
262}
263
264void HandleGotoDefinitionRequest(GotoDefinitionRequest request,
265 MessageWriter writer) {
266 GotoDefinitionResponse response;
267 response.set_id(request.id());
268
269 SourceId id =
270 SourceFileMap::GetSourceId(request.params().textDocument().uri());
271
272 // Unknown source files cause an empty response which corresponds with
273 // the definition not beeing found.
274 if (!id.IsValid()) {
275 response.SetNull("result");
276 writer(std::move(response.GetJsonValue()));
277 return;
278 }
279
280 auto pos =
281 LineAndColumn::WithUnknownOffset(request.params().position().line(),
282 request.params().position().character());
283
284 if (auto maybe_definition = LanguageServerData::FindDefinition(id, pos)) {
285 SourcePosition definition = *maybe_definition;
286 response.result().SetTo(definition);
287 } else {
288 response.SetNull("result");
289 }
290
291 writer(std::move(response.GetJsonValue()));
292}
293
294void HandleChangeWatchedFilesNotification(
296 // TODO(szuend): Implement updates to the TorqueFile list when create/delete
297 // notifications are received. Currently we simply re-compile.
298 RecompileTorqueWithDiagnostics(writer);
299}
300
301void HandleDocumentSymbolRequest(DocumentSymbolRequest request,
302 MessageWriter writer) {
303 DocumentSymbolResponse response;
304 response.set_id(request.id());
305
306 SourceId id =
307 SourceFileMap::GetSourceId(request.params().textDocument().uri());
308
309 for (const auto& symbol : LanguageServerData::SymbolsForSourceId(id)) {
310 DCHECK(symbol->IsUserDefined());
311 if (symbol->IsMacro()) {
312 Macro* macro = Macro::cast(symbol);
313 SymbolInformation info = response.add_result();
314 info.set_name(macro->ReadableName());
315 info.set_kind(SymbolKind::kFunction);
316 info.location().SetTo(macro->Position());
317 } else if (symbol->IsBuiltin()) {
318 Builtin* builtin = Builtin::cast(symbol);
319 SymbolInformation info = response.add_result();
320 info.set_name(builtin->ReadableName());
321 info.set_kind(SymbolKind::kFunction);
322 info.location().SetTo(builtin->Position());
323 } else if (symbol->IsGenericCallable()) {
324 GenericCallable* generic = GenericCallable::cast(symbol);
325 SymbolInformation info = response.add_result();
326 info.set_name(generic->name());
327 info.set_kind(SymbolKind::kFunction);
328 info.location().SetTo(generic->Position());
329 } else if (symbol->IsTypeAlias()) {
330 const Type* type = TypeAlias::cast(symbol)->type();
332 type->IsClassType() ? SymbolKind::kClass : SymbolKind::kStruct;
333
334 SymbolInformation sym = response.add_result();
335 sym.set_name(type->ToString());
336 sym.set_kind(kind);
337 sym.location().SetTo(symbol->Position());
338 }
339 }
340
341 // Trigger empty array creation in case no symbols were found.
342 USE(response.result_size());
343
344 writer(std::move(response.GetJsonValue()));
345}
346
347} // namespace
348
349void HandleMessage(JsonValue raw_message, MessageWriter writer) {
350 Request<bool> request(std::move(raw_message));
351
352 // We ignore responses for now. They are matched to requests
353 // by id and don't have a method set.
354 // TODO(szuend): Implement proper response handling for requests
355 // that originate from the server.
356 if (!request.has_method()) {
357 Logger::Log("[info] Unhandled response with id ", request.id(), "\n\n");
358 return;
359 }
360
361 const std::string method = request.method();
362 if (method == "initialize") {
363 HandleInitializeRequest(
364 InitializeRequest(std::move(request.GetJsonValue())), writer);
365 } else if (method == "initialized") {
366 HandleInitializedNotification(writer);
367 } else if (method == "torque/fileList") {
368 HandleTorqueFileListNotification(
369 TorqueFileListNotification(std::move(request.GetJsonValue())), writer);
370 } else if (method == "textDocument/definition") {
371 HandleGotoDefinitionRequest(
372 GotoDefinitionRequest(std::move(request.GetJsonValue())), writer);
373 } else if (method == "workspace/didChangeWatchedFiles") {
374 HandleChangeWatchedFilesNotification(
376 writer);
377 } else if (method == "textDocument/documentSymbol") {
378 HandleDocumentSymbolRequest(
379 DocumentSymbolRequest(std::move(request.GetJsonValue())), writer);
380 } else {
381 Logger::Log("[error] Message of type ", method, " is not handled!\n\n");
382 }
383}
384
385} // namespace ls
386} // namespace torque
387} // namespace internal
388} // namespace v8
Builtins::Kind kind
Definition builtins.cc:40
SourcePosition pos
static VarType & Get()
Definition contextual.h:64
static void Abort()
static V8_EXPORT_PRIVATE std::optional< SourcePosition > FindDefinition(SourceId source, LineAndColumn pos)
static const Symbols & SymbolsForSourceId(SourceId id)
Definition server-data.h:57
static void Log(Args &&... args)
Definition globals.h:33
static SourceId GetSourceId(const std::string &path)
static std::string AbsolutePath(SourceId file)
#define EXPORT_CONTEXTUAL_VARIABLE(VarName)
Definition contextual.h:94
DirectHandle< JSReceiver > options
ZoneVector< RpoNumber > & result
LiftoffRegister reg
int position
Definition liveedit.cc:290
std::map< SourceId, PublishDiagnosticsNotification > notifications_
bool suppress_lint_messages_
Range(V< T >, V< T >, V< T >) -> Range< T >
ResponseArrayResult< SymbolInformation > DocumentSymbolResponse
Definition message.h:365
Request< DocumentSymbolParams > DocumentSymbolRequest
Definition message.h:339
void WriteMessage(JsonValue message)
Request< DidChangeWatchedFilesParams > DidChangeWatchedFilesNotification
Definition message.h:337
Request< TextDocumentPositionParams > GotoDefinitionRequest
Definition message.h:336
Response< InitializeResult > InitializeResponse
Definition message.h:351
static const size_t kContentLengthSize
std::function< void(JsonValue)> MessageWriter
Request< RegistrationParams > RegistrationRequest
Definition message.h:334
std::string SerializeToString(const JsonValue &value)
Definition json.cc:60
Response< Location > GotoDefinitionResponse
Definition message.h:352
constexpr const char * kProtocolLineEnding
Request< InitializeParams > InitializeRequest
Definition message.h:333
static const char kContentLength[]
void HandleMessage(JsonValue raw_message, MessageWriter writer)
Request< FileListParams > TorqueFileListNotification
Definition message.h:335
JsonParserResult ParseJson(const std::string &input)
Request< PublishDiagnosticsParams > PublishDiagnosticsNotification
Definition message.h:338
void CompilationFinished(TorqueCompilerResult result, MessageWriter writer)
TorqueCompilerResult CompileTorque(const std::string &source, TorqueCompilerOptions options)
#define CHECK(condition)
Definition logging.h:124
#define CHECK_EQ(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
#define USE(...)
Definition macros.h:293
static LineAndColumn WithUnknownOffset(int line, int column)
Symbol method