#include #include #include #include #include #include #include #include #include #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 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& out() const { return out_; } private: std::deque in_; std::vector 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(); 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(); 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")); }