132 lines
4.6 KiB
C++
132 lines
4.6 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
#include <deque>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <nlohmann/json.hpp>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "fold/lsp/diagnostic.hpp"
|
|
#include "fold/lsp/server.hpp"
|
|
#include "fold/lsp/transport.hpp"
|
|
|
|
namespace {
|
|
|
|
class ScriptedTransport final : public fold::lsp::Transport {
|
|
public:
|
|
void enqueue(const nlohmann::json& msg) { in_.push_back(msg.dump()); }
|
|
std::optional<std::string> readMessage() override {
|
|
if (in_.empty())
|
|
return std::nullopt;
|
|
std::string m = std::move(in_.front());
|
|
in_.pop_front();
|
|
return m;
|
|
}
|
|
void writeMessage(const nlohmann::json& m) override { out_.push_back(m); }
|
|
const std::vector<nlohmann::json>& out() const { return out_; }
|
|
|
|
private:
|
|
std::deque<std::string> in_;
|
|
std::vector<nlohmann::json> out_;
|
|
};
|
|
|
|
std::string locateFixtures() {
|
|
const char* env = std::getenv("FOLD_FIXTURES_DIR");
|
|
std::string fixtures = env ? env : "../fixtures";
|
|
if (!std::filesystem::exists(fixtures + "/clones/exact.cpp")) {
|
|
fixtures = std::filesystem::current_path().string() + "/../fixtures";
|
|
}
|
|
REQUIRE(std::filesystem::exists(fixtures + "/clones/exact.cpp"));
|
|
return fixtures;
|
|
}
|
|
|
|
void writeCompileDb(const std::string& fixtures) {
|
|
namespace fs = std::filesystem;
|
|
nlohmann::json db = nlohmann::json::array();
|
|
for (const auto& f : {"clones/exact.cpp", "clones/renamed.cpp"}) {
|
|
fs::path file = fs::path(fixtures) / f;
|
|
db.push_back({
|
|
{"directory", fixtures},
|
|
{"command", std::string("clang++ -std=c++20 -c ") + file.string()},
|
|
{"file", file.string()},
|
|
});
|
|
}
|
|
std::ofstream(fs::path(fixtures) / "compile_commands.json") << db.dump();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("LSP: initialize advertises codeActionProvider", "[lsp][codeaction][handshake]") {
|
|
auto transport = std::make_unique<ScriptedTransport>();
|
|
auto* t = transport.get();
|
|
t->enqueue({{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "initialize"},
|
|
{"params", {{"rootUri", "file:///tmp"}}}});
|
|
t->enqueue({{"jsonrpc", "2.0"}, {"id", 2}, {"method", "shutdown"}});
|
|
t->enqueue({{"jsonrpc", "2.0"}, {"method", "exit"}});
|
|
|
|
fold::lsp::Server server(std::move(transport));
|
|
REQUIRE(server.run() == 0);
|
|
REQUIRE(t->out().size() == 2);
|
|
const auto& caps = t->out()[0]["result"]["capabilities"];
|
|
REQUIRE(caps.contains("codeActionProvider"));
|
|
}
|
|
|
|
TEST_CASE("LSP: codeAction returns actions for a saved-file finding", "[lsp][codeaction][e2e]") {
|
|
auto fixtures = locateFixtures();
|
|
writeCompileDb(fixtures);
|
|
std::string clone_file = fixtures + "/clones/exact.cpp";
|
|
std::string clone_uri = fold::lsp::uriFromAbsolutePath(clone_file);
|
|
|
|
auto transport = std::make_unique<ScriptedTransport>();
|
|
auto* t = transport.get();
|
|
t->enqueue({{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "initialize"},
|
|
{"params",
|
|
{
|
|
{"rootUri", fold::lsp::uriFromAbsolutePath(fixtures)},
|
|
}}});
|
|
t->enqueue({{"jsonrpc", "2.0"}, {"method", "initialized"}});
|
|
t->enqueue({{"jsonrpc", "2.0"},
|
|
{"method", "textDocument/didSave"},
|
|
{"params",
|
|
{
|
|
{"textDocument", {{"uri", clone_uri}}},
|
|
}}});
|
|
t->enqueue({{"jsonrpc", "2.0"},
|
|
{"id", 2},
|
|
{"method", "textDocument/codeAction"},
|
|
{"params",
|
|
{
|
|
{"textDocument", {{"uri", clone_uri}}},
|
|
{"range",
|
|
{
|
|
{"start", {{"line", 0}, {"character", 0}}},
|
|
{"end", {{"line", 100}, {"character", 0}}},
|
|
}},
|
|
}}});
|
|
t->enqueue({{"jsonrpc", "2.0"}, {"id", 3}, {"method", "shutdown"}});
|
|
t->enqueue({{"jsonrpc", "2.0"}, {"method", "exit"}});
|
|
|
|
fold::lsp::Server server(std::move(transport));
|
|
REQUIRE(server.run() == 0);
|
|
|
|
// Find the codeAction response (id == 2).
|
|
nlohmann::json action_resp;
|
|
for (const auto& m : t->out()) {
|
|
if (m.value("id", -1) == 2)
|
|
action_resp = m;
|
|
}
|
|
REQUIRE_FALSE(action_resp.is_null());
|
|
REQUIRE(action_resp.contains("result"));
|
|
auto actions = action_resp["result"];
|
|
REQUIRE(actions.is_array());
|
|
REQUIRE(!actions.empty());
|
|
// Each action must have a title and a kind.
|
|
REQUIRE(actions[0].contains("title"));
|
|
REQUIRE(actions[0].contains("kind"));
|
|
}
|