| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/torque/ls/json.h" |
| #include "src/torque/ls/message-handler.h" |
| #include "src/torque/ls/message.h" |
| #include "src/torque/server-data.h" |
| #include "src/torque/source-positions.h" |
| #include "test/unittests/test-utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace torque { |
| namespace ls { |
| |
| TEST(LanguageServerMessage, InitializeRequest) { |
| InitializeRequest request; |
| request.set_id(5); |
| request.set_method("initialize"); |
| request.params(); |
| |
| bool writer_called = false; |
| HandleMessage(std::move(request.GetJsonValue()), [&](JsonValue raw_response) { |
| InitializeResponse response(std::move(raw_response)); |
| |
| // Check that the response id matches up with the request id, and that |
| // the language server signals its support for definitions. |
| EXPECT_EQ(response.id(), 5); |
| EXPECT_TRUE(response.result().capabilities().definitionProvider()); |
| EXPECT_TRUE(response.result().capabilities().documentSymbolProvider()); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| TEST(LanguageServerMessage, |
| RegisterDynamicCapabilitiesAfterInitializedNotification) { |
| Request<bool> notification; |
| notification.set_method("initialized"); |
| |
| bool writer_called = false; |
| HandleMessage(std::move(notification.GetJsonValue()), [&](JsonValue |
| raw_request) { |
| RegistrationRequest request(std::move(raw_request)); |
| |
| ASSERT_EQ(request.method(), "client/registerCapability"); |
| ASSERT_EQ(request.params().registrations_size(), (size_t)1); |
| |
| Registration registration = request.params().registrations(0); |
| ASSERT_EQ(registration.method(), "workspace/didChangeWatchedFiles"); |
| |
| auto options = |
| registration |
| .registerOptions<DidChangeWatchedFilesRegistrationOptions>(); |
| ASSERT_EQ(options.watchers_size(), (size_t)1); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| TEST(LanguageServerMessage, GotoDefinitionUnkownFile) { |
| SourceFileMap::Scope source_file_map_scope(""); |
| |
| GotoDefinitionRequest request; |
| request.set_id(42); |
| request.set_method("textDocument/definition"); |
| request.params().textDocument().set_uri("file:///unknown.tq"); |
| |
| bool writer_called = false; |
| HandleMessage(std::move(request.GetJsonValue()), [&](JsonValue raw_response) { |
| GotoDefinitionResponse response(std::move(raw_response)); |
| EXPECT_EQ(response.id(), 42); |
| EXPECT_TRUE(response.IsNull("result")); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| TEST(LanguageServerMessage, GotoDefinition) { |
| SourceFileMap::Scope source_file_map_scope(""); |
| SourceId test_id = SourceFileMap::AddSource("file://test.tq"); |
| SourceId definition_id = SourceFileMap::AddSource("file://base.tq"); |
| |
| LanguageServerData::Scope server_data_scope; |
| LanguageServerData::AddDefinition({test_id, {1, 0}, {1, 10}}, |
| {definition_id, {4, 1}, {4, 5}}); |
| |
| // First, check a unknown definition. The result must be null. |
| GotoDefinitionRequest request; |
| request.set_id(42); |
| request.set_method("textDocument/definition"); |
| request.params().textDocument().set_uri("file://test.tq"); |
| request.params().position().set_line(2); |
| request.params().position().set_character(0); |
| |
| bool writer_called = false; |
| HandleMessage(std::move(request.GetJsonValue()), [&](JsonValue raw_response) { |
| GotoDefinitionResponse response(std::move(raw_response)); |
| EXPECT_EQ(response.id(), 42); |
| EXPECT_TRUE(response.IsNull("result")); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| |
| // Second, check a known defintion. |
| request = GotoDefinitionRequest(); |
| request.set_id(43); |
| request.set_method("textDocument/definition"); |
| request.params().textDocument().set_uri("file://test.tq"); |
| request.params().position().set_line(1); |
| request.params().position().set_character(5); |
| |
| writer_called = false; |
| HandleMessage(std::move(request.GetJsonValue()), [&](JsonValue raw_response) { |
| GotoDefinitionResponse response(std::move(raw_response)); |
| EXPECT_EQ(response.id(), 43); |
| ASSERT_FALSE(response.IsNull("result")); |
| |
| Location location = response.result(); |
| EXPECT_EQ(location.uri(), "file://base.tq"); |
| EXPECT_EQ(location.range().start().line(), 4); |
| EXPECT_EQ(location.range().start().character(), 1); |
| EXPECT_EQ(location.range().end().line(), 4); |
| EXPECT_EQ(location.range().end().character(), 5); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| TEST(LanguageServerMessage, CompilationErrorSendsDiagnostics) { |
| DiagnosticsFiles::Scope diagnostic_files_scope; |
| LanguageServerData::Scope server_data_scope; |
| TorqueMessages::Scope messages_scope; |
| SourceFileMap::Scope source_file_map_scope(""); |
| |
| TorqueCompilerResult result; |
| { Error("compilation failed somehow"); } |
| result.messages = std::move(TorqueMessages::Get()); |
| result.source_file_map = SourceFileMap::Get(); |
| |
| bool writer_called = false; |
| CompilationFinished(std::move(result), [&](JsonValue raw_response) { |
| PublishDiagnosticsNotification notification(std::move(raw_response)); |
| |
| EXPECT_EQ(notification.method(), "textDocument/publishDiagnostics"); |
| ASSERT_FALSE(notification.IsNull("params")); |
| EXPECT_EQ(notification.params().uri(), "<unknown>"); |
| |
| ASSERT_GT(notification.params().diagnostics_size(), static_cast<size_t>(0)); |
| Diagnostic diagnostic = notification.params().diagnostics(0); |
| EXPECT_EQ(diagnostic.severity(), Diagnostic::kError); |
| EXPECT_EQ(diagnostic.message(), "compilation failed somehow"); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| TEST(LanguageServerMessage, LintErrorSendsDiagnostics) { |
| DiagnosticsFiles::Scope diagnostic_files_scope; |
| TorqueMessages::Scope messages_scope; |
| LanguageServerData::Scope server_data_scope; |
| SourceFileMap::Scope sourc_file_map_scope(""); |
| SourceId test_id = SourceFileMap::AddSource("file://test.tq"); |
| |
| // No compilation errors but two lint warnings. |
| { |
| SourcePosition pos1{test_id, {0, 0}, {0, 1}}; |
| SourcePosition pos2{test_id, {1, 0}, {1, 1}}; |
| Lint("lint error 1").Position(pos1); |
| Lint("lint error 2").Position(pos2); |
| } |
| |
| TorqueCompilerResult result; |
| result.messages = std::move(TorqueMessages::Get()); |
| result.source_file_map = SourceFileMap::Get(); |
| |
| bool writer_called = false; |
| CompilationFinished(std::move(result), [&](JsonValue raw_response) { |
| PublishDiagnosticsNotification notification(std::move(raw_response)); |
| |
| EXPECT_EQ(notification.method(), "textDocument/publishDiagnostics"); |
| ASSERT_FALSE(notification.IsNull("params")); |
| EXPECT_EQ(notification.params().uri(), "file://test.tq"); |
| |
| ASSERT_EQ(notification.params().diagnostics_size(), static_cast<size_t>(2)); |
| Diagnostic diagnostic1 = notification.params().diagnostics(0); |
| EXPECT_EQ(diagnostic1.severity(), Diagnostic::kWarning); |
| EXPECT_EQ(diagnostic1.message(), "lint error 1"); |
| |
| Diagnostic diagnostic2 = notification.params().diagnostics(1); |
| EXPECT_EQ(diagnostic2.severity(), Diagnostic::kWarning); |
| EXPECT_EQ(diagnostic2.message(), "lint error 2"); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| TEST(LanguageServerMessage, CleanCompileSendsNoDiagnostics) { |
| LanguageServerData::Scope server_data_scope; |
| SourceFileMap::Scope sourc_file_map_scope(""); |
| |
| TorqueCompilerResult result; |
| result.source_file_map = SourceFileMap::Get(); |
| |
| CompilationFinished(std::move(result), [](JsonValue raw_response) { |
| FAIL() << "Sending unexpected response!"; |
| }); |
| } |
| |
| TEST(LanguageServerMessage, NoSymbolsSendsEmptyResponse) { |
| LanguageServerData::Scope server_data_scope; |
| SourceFileMap::Scope sourc_file_map_scope(""); |
| |
| DocumentSymbolRequest request; |
| request.set_id(42); |
| request.set_method("textDocument/documentSymbol"); |
| request.params().textDocument().set_uri("file://test.tq"); |
| |
| bool writer_called = false; |
| HandleMessage(std::move(request.GetJsonValue()), [&](JsonValue raw_response) { |
| DocumentSymbolResponse response(std::move(raw_response)); |
| EXPECT_EQ(response.id(), 42); |
| EXPECT_EQ(response.result_size(), static_cast<size_t>(0)); |
| |
| writer_called = true; |
| }); |
| EXPECT_TRUE(writer_called); |
| } |
| |
| } // namespace ls |
| } // namespace torque |
| } // namespace internal |
| } // namespace v8 |