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