module; export module lsp.test.provider.json_flow; import std; import lsp.test.framework; import lsp.provider.initialize.initialize; import lsp.provider.text_document.did_open; import lsp.provider.text_document.completion; import lsp.provider.completion_item.resolve; import lsp.provider.text_document.definition; import lsp.core.dispacther; import lsp.manager.manager_hub; import lsp.scheduler.async_executor; import lsp.protocol; import lsp.codec.facade; import lsp.test.provider.fixtures; export namespace lsp::test::provider { class JsonFlowTests { public: static void Register(TestRunner& runner); private: static TestResult TestInitializeCompletionResolveDefinition(); }; } namespace lsp::test::provider { namespace { struct ProviderEnv { scheduler::AsyncExecutor scheduler{ 1 }; manager::ManagerHub hub{}; core::ExecutionContext context; ProviderEnv() : context([](core::ServerLifecycleEvent) {}, scheduler, hub) { hub.Initialize(); } }; template std::string SerializeOrThrow(const T& obj) { auto json = codec::Serialize(obj); assertTrue(json.has_value(), "Failed to serialize LSP JSON"); return json.value(); } template T DeserializeOrThrow(const std::string& json) { auto parsed = codec::Deserialize(json); assertTrue(parsed.has_value(), "Failed to deserialize LSP JSON"); return parsed.value(); } protocol::ResponseMessage ParseResponse(const std::string& json) { auto parsed = codec::Deserialize(json); assertTrue(parsed.has_value(), "Failed to deserialize response"); return parsed.value(); } protocol::Position FindPosition(const std::string& content, const std::string& marker, bool after_marker) { auto pos = content.find(marker); assertTrue(pos != std::string::npos, "Marker not found in fixture"); protocol::Position result{}; result.line = 0; result.character = 0; for (std::size_t i = 0; i < pos; ++i) { if (content[i] == '\n') { result.line++; result.character = 0; } else { result.character++; } } if (after_marker) { result.character += static_cast(marker.size()); } return result; } std::optional FindItem(const std::vector& items, const std::string& label) { auto it = std::find_if(items.begin(), items.end(), [&](const auto& item) { return item.label == label; }); if (it == items.end()) { return std::nullopt; } return *it; } } void JsonFlowTests::Register(TestRunner& runner) { runner.addTest("json flow initialize->completion->resolve->definition", TestInitializeCompletionResolveDefinition); } TestResult JsonFlowTests::TestInitializeCompletionResolveDefinition() { TestResult result{ "", true, "ok" }; ProviderEnv env; auto workspace_uri = ToUri(FixturePath("workspace")); protocol::LSPArray folders; folders.emplace_back(protocol::LSPObject{ { "uri", workspace_uri }, { "name", "workspace" } }); protocol::LSPObject init_params; init_params["trace"] = protocol::string(protocol::TraceValueLiterals::Off); init_params["workspaceFolders"] = folders; protocol::RequestMessage init_request; init_request.id = "init"; init_request.method = "initialize"; init_request.params = protocol::LSPAny(init_params); auto init_json = SerializeOrThrow(init_request); auto parsed_init = DeserializeOrThrow(init_json); ::lsp::provider::Initialize init_provider; auto init_response_json = init_provider.ProvideResponse(parsed_init, env.context); auto init_response = ParseResponse(init_response_json); assertTrue(init_response.result.has_value(), "Initialize should return result"); env.scheduler.WaitAll(); auto path = FixturePath("main_unit.tsf"); auto content = ReadTextFile(path); auto uri = ToUri(path); protocol::LSPObject text_doc{ { "uri", uri }, { "languageId", "tsl" }, { "version", 1 }, { "text", content } }; protocol::NotificationMessage open_message; open_message.method = "textDocument/didOpen"; open_message.params = protocol::LSPAny(protocol::LSPObject{ { "textDocument", protocol::LSPAny(text_doc) } }); auto open_json = SerializeOrThrow(open_message); auto parsed_open = DeserializeOrThrow(open_json); ::lsp::provider::text_document::DidOpen open_provider; open_provider.HandleNotification(parsed_open, env.context); auto completion_pos = FindPosition(content, "new Wid", true); protocol::LSPObject completion_params{ { "textDocument", protocol::LSPObject{ { "uri", uri } } }, { "position", protocol::LSPObject{ { "line", completion_pos.line }, { "character", completion_pos.character } } } }; protocol::RequestMessage completion_request; completion_request.id = "c1"; completion_request.method = "textDocument/completion"; completion_request.params = protocol::LSPAny(completion_params); auto completion_json = SerializeOrThrow(completion_request); auto parsed_completion = DeserializeOrThrow(completion_json); ::lsp::provider::text_document::Completion completion_provider; auto completion_response_json = completion_provider.ProvideResponse(parsed_completion, env.context); auto completion_response = ParseResponse(completion_response_json); auto list = codec::FromLSPAny.template operator()(completion_response.result.value()); auto item = FindItem(list.items, "Widget"); assertTrue(item.has_value(), "Expected Widget completion"); protocol::RequestMessage resolve_request; resolve_request.id = "r1"; resolve_request.method = "completionItem/resolve"; resolve_request.params = codec::ToLSPAny(*item); auto resolve_json = SerializeOrThrow(resolve_request); auto parsed_resolve = DeserializeOrThrow(resolve_json); ::lsp::provider::completion_item::Resolve resolve_provider; auto resolve_response_json = resolve_provider.ProvideResponse(parsed_resolve, env.context); auto resolve_response = ParseResponse(resolve_response_json); auto resolved = codec::FromLSPAny.template operator()(resolve_response.result.value()); assertTrue(resolved.insertText.has_value(), "Resolved item should have insertText"); auto def_pos = FindPosition(content, "UnitFunc(1);", false); protocol::LSPObject definition_params{ { "textDocument", protocol::LSPObject{ { "uri", uri } } }, { "position", protocol::LSPObject{ { "line", def_pos.line }, { "character", def_pos.character } } } }; protocol::RequestMessage def_request; def_request.id = "d1"; def_request.method = "textDocument/definition"; def_request.params = protocol::LSPAny(definition_params); auto def_json = SerializeOrThrow(def_request); auto parsed_def = DeserializeOrThrow(def_json); ::lsp::provider::text_document::Definition def_provider; auto def_response_json = def_provider.ProvideResponse(parsed_def, env.context); auto def_response = ParseResponse(def_response_json); auto location = codec::FromLSPAny.template operator()(def_response.result.value()); auto expected_pos = FindPosition(content, "function UnitFunc", false); assertTrue(location.range.start.line == expected_pos.line, "Definition should resolve in document"); return result; } }