- LSPMessageValidator: JSON-RPC 2.0 shape validation (method, id type, params type) - LSPCapabilityNegotiator: per-source capability tracking; shouldRoute gates on declared caps - LSPRequestThrottler: per-URI/method debounce; blocks rapid didChange floods - LSPErrorRecovery: per-source health tracking; stale-cache fallback when crashed - Sprint278IntegrationSummary: poly-parse (C++ + Haskell) end-to-end integration 26/26 tests passing (5+5+5+5+6). All new headers well under 600 lines. LoRA recorded: sprint278-2026-03-01 (9 calls, 6437 tokens). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
136 lines
5.4 KiB
C++
136 lines
5.4 KiB
C++
// Step 1922: Sprint 278 Integration
|
|
// poly-parse project: C++ lexer component + Haskell AST transform.
|
|
// All four hardening components work together:
|
|
// - Validator rejects malformed messages
|
|
// - Negotiator blocks methods haskell-lsp doesn't support
|
|
// - Throttler debounces rapid didChange from the C++ lexer
|
|
// - ErrorRecovery serves stale diags when haskell-lsp crashes
|
|
//
|
|
// t1: validator rejects bad message before routing
|
|
// t2: negotiator blocks referencesProvider (haskell-lsp doesn't declare it)
|
|
// t3: throttler debounces rapid didChange on lexer.cpp
|
|
// t4: error recovery serves stale haskell diags after crash
|
|
// t5: healthy gopls still routes normally; haskell-lsp in recovery
|
|
// t6: Sprint278IntegrationSummary reports complete
|
|
|
|
#include "LSPMessageValidator.h"
|
|
#include "LSPCapabilityNegotiator.h"
|
|
#include "LSPRequestThrottler.h"
|
|
#include "LSPErrorRecovery.h"
|
|
#include "Sprint278IntegrationSummary.h"
|
|
#include <iostream>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
using json = nlohmann::json;
|
|
namespace ws = whetstone;
|
|
|
|
static int p=0,f=0;
|
|
#define T(n) { std::cout<<" "<<#n<<"... "; }
|
|
#define P() { std::cout<<"PASS\n"; ++p; }
|
|
#define F(m) { std::cout<<"FAIL: "<<m<<"\n"; ++f; }
|
|
#define C(c,m) if(!(c)){F(m);return;}
|
|
|
|
// poly-parse setup:
|
|
// gopls → C++ lexer (hoverProvider, definitionProvider, referencesProvider)
|
|
// haskell-lsp → Haskell AST transform (hoverProvider, definitionProvider)
|
|
|
|
void t1(){
|
|
T(validator_rejects_malformed_message);
|
|
ws::LSPMessageValidator v;
|
|
// message missing "jsonrpc"
|
|
json bad = {{"method","textDocument/hover"},{"id",1}};
|
|
C(!v.validate(bad).valid, "missing jsonrpc rejected");
|
|
// valid message passes
|
|
json good = {{"jsonrpc","2.0"},{"id",1},{"method","textDocument/hover"}};
|
|
C(v.validate(good).valid, "valid message passes");
|
|
P();
|
|
}
|
|
|
|
void t2(){
|
|
T(negotiator_blocks_unsupported_method);
|
|
ws::LSPCapabilityNegotiator n;
|
|
n.registerSource("gopls", {"hoverProvider","definitionProvider","referencesProvider"});
|
|
n.registerSource("haskell-lsp", {"hoverProvider","definitionProvider"});
|
|
// gopls supports references → route
|
|
C( n.shouldRoute("gopls", "textDocument/references"), "gopls routes references");
|
|
// haskell-lsp lacks referencesProvider → block
|
|
C(!n.shouldRoute("haskell-lsp", "textDocument/references"), "haskell-lsp blocked for references");
|
|
// both support hover → route
|
|
C( n.shouldRoute("gopls", "textDocument/hover"), "gopls routes hover");
|
|
C( n.shouldRoute("haskell-lsp", "textDocument/hover"), "haskell-lsp routes hover");
|
|
P();
|
|
}
|
|
|
|
void t3(){
|
|
T(throttler_debounces_rapid_didChange);
|
|
ws::LSPRequestThrottler t;
|
|
const std::string uri = "file:///src/lexer.cpp";
|
|
const std::string method = "textDocument/didChange";
|
|
const int64_t debounce = 300; // ms
|
|
// first request: allowed
|
|
C( t.shouldAllow(uri, method, 0, debounce), "first allowed");
|
|
t.record(uri, method, 0);
|
|
// 100ms later: blocked
|
|
C(!t.shouldAllow(uri, method, 100, debounce), "100ms blocked");
|
|
// 300ms later: allowed
|
|
C( t.shouldAllow(uri, method, 300, debounce), "300ms allowed");
|
|
t.record(uri, method, 300);
|
|
// hover not throttled
|
|
C( t.shouldAllow(uri, "textDocument/hover", 350, debounce), "hover not throttled");
|
|
P();
|
|
}
|
|
|
|
void t4(){
|
|
T(error_recovery_serves_stale_diags_after_crash);
|
|
ws::LSPErrorRecovery r;
|
|
ws::Diagnostic d;
|
|
d.uri="file:///src/AST.hs"; d.severity=1;
|
|
d.message="parse error"; d.source="haskell-lsp";
|
|
r.cacheDiagnostics("haskell-lsp","file:///src/AST.hs",{d});
|
|
// healthy: no fallback
|
|
C(r.fallbackDiagnostics("haskell-lsp","file:///src/AST.hs").empty(),
|
|
"healthy: no fallback");
|
|
// crash
|
|
r.markCrashed("haskell-lsp");
|
|
auto fb = r.fallbackDiagnostics("haskell-lsp","file:///src/AST.hs");
|
|
C(fb.size() == 1, "one stale diag");
|
|
C(fb[0].message == "parse error", "correct stale message");
|
|
C(r.crashedCount() == 1, "one crashed source");
|
|
P();
|
|
}
|
|
|
|
void t5(){
|
|
T(healthy_gopls_routes_normally);
|
|
ws::LSPCapabilityNegotiator n;
|
|
ws::LSPErrorRecovery r;
|
|
n.registerSource("gopls", {"hoverProvider","definitionProvider","referencesProvider"});
|
|
n.registerSource("haskell-lsp", {"hoverProvider","definitionProvider"});
|
|
r.markCrashed("haskell-lsp");
|
|
// gopls healthy and has capability → route
|
|
C(r.isHealthy("gopls"), "gopls healthy");
|
|
C(n.shouldRoute("gopls","textDocument/hover"), "gopls hover routed");
|
|
// haskell-lsp crashed but its hover capability is still registered
|
|
// (routing decision is separate from health — health gates fallback logic)
|
|
C(!r.isHealthy("haskell-lsp"), "haskell-lsp crashed");
|
|
auto healthy = r.healthySources({"gopls","haskell-lsp"});
|
|
C(healthy.size() == 1, "one healthy source");
|
|
C(healthy[0] == "gopls","gopls is the healthy one");
|
|
P();
|
|
}
|
|
|
|
void t6(){
|
|
T(sprint278_integration_summary_complete);
|
|
ws::Sprint278IntegrationSummary s;
|
|
C(s.stepsCompleted == 5, "5 steps");
|
|
C(s.success, "success");
|
|
C(s.sprintName().find("278") != std::string::npos, "278 in name");
|
|
P();
|
|
}
|
|
|
|
int main(){
|
|
std::cout << "Step 1922: Sprint 278 Integration (poly-parse: C++ + Haskell)\n";
|
|
t1(); t2(); t3(); t4(); t5(); t6();
|
|
std::cout << "\n" << p << "/" << (p+f) << " passed\n";
|
|
return f > 0 ? 1 : 0;
|
|
}
|