tsl-devkit/lsp-server/test/test_provider/json_flow_test.cppm

223 lines
8.6 KiB
C++

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.dispatcher;
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<typename T>
std::string SerializeOrThrow(const T& obj)
{
auto json = codec::Serialize(obj);
assertTrue(json.has_value(), "Failed to serialize LSP JSON");
return json.value();
}
template<typename T>
T DeserializeOrThrow(const std::string& json)
{
auto parsed = codec::Deserialize<T>(json);
assertTrue(parsed.has_value(), "Failed to deserialize LSP JSON");
return parsed.value();
}
protocol::ResponseMessage ParseResponse(const std::string& json)
{
auto parsed = codec::Deserialize<protocol::ResponseMessage>(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<std::uint32_t>(marker.size());
}
return result;
}
std::optional<protocol::CompletionItem> FindItem(const std::vector<protocol::CompletionItem>& 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<protocol::RequestMessage>(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<protocol::NotificationMessage>(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<protocol::RequestMessage>(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()<protocol::CompletionList>(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<protocol::RequestMessage>(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()<protocol::CompletionItem>(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<protocol::RequestMessage>(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()<protocol::Location>(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;
}
}