- LSPWorkspaceIndex: track open docs, URI→{languageId,content,version}
- LSPDiagnosticsAggregator: merge diagnostics from multiple LSP sources per URI
- LSPCompletionMerger: merge+deduplicate completion items from multiple sources
- LSPOrchestrator: top-level coordinator composing all three + proxy + cross-lang resolver
- Sprint277IntegrationSummary: poly-api (Go+TypeScript) end-to-end integration
26/26 tests passing (5+5+5+5+6). All new headers ≤600 lines.
LoRA recorded: sprint277-2026-03-01 (1 call, 569 tokens).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
141 lines
5.6 KiB
C++
141 lines
5.6 KiB
C++
// Step 1916: LSPOrchestrator
|
|
// Top-level coordinator: workspace index + cross-lang definition routing +
|
|
// diagnostics aggregation + completion merging.
|
|
//
|
|
// t1: didOpen/didChange/didClose update workspace index via handle()
|
|
// t2: textDocument/definition routes cross-language via proxy
|
|
// t3: publishDiagnostics and getDiagnostics
|
|
// t4: registerCompletions and getCompletions (merged, deduped)
|
|
// t5: summary() reflects current state
|
|
|
|
#include "LSPOrchestrator.h"
|
|
#include "ABIBoundaryExtractor.h"
|
|
#include "CrossLanguageSymbolTable.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;}
|
|
|
|
static ws::CrossLanguageSymbolTable buildTable() {
|
|
ws::CrossLanguageSymbolTable t;
|
|
ws::ABIBoundaryNode n;
|
|
n.name="HandleRequest"; n.kind="function";
|
|
n.fromComponent="api-client"; n.toComponent="api-server";
|
|
n.fromLanguage="TypeScript"; n.toLanguage="Go";
|
|
n.signature="void HandleRequest(const char* body)";
|
|
t.insert(n);
|
|
return t;
|
|
}
|
|
|
|
void t1(){
|
|
T(didOpen_didChange_didClose_via_handle);
|
|
auto table = buildTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
// didOpen
|
|
json openMsg = {{"jsonrpc","2.0"},{"method","textDocument/didOpen"},
|
|
{"params",{{"textDocument",{{"uri","file:///api.go"},{"languageId","go"},
|
|
{"text","package main"},{"version",1}}}}}};
|
|
orc.handle(openMsg);
|
|
C(orc.workspaceIndex().contains("file:///api.go"), "doc opened");
|
|
C(orc.workspaceIndex().getLanguage("file:///api.go") == "go", "language go");
|
|
// didChange
|
|
json changeMsg = {{"jsonrpc","2.0"},{"method","textDocument/didChange"},
|
|
{"params",{{"textDocument",{{"uri","file:///api.go"},{"version",2}}},
|
|
{"contentChanges",json::array({json{{"text","package api"}}})}}}};
|
|
orc.handle(changeMsg);
|
|
C(orc.workspaceIndex().getDocument("file:///api.go").content == "package api", "content updated");
|
|
C(orc.workspaceIndex().getDocument("file:///api.go").version == 2, "version updated");
|
|
// didClose
|
|
json closeMsg = {{"jsonrpc","2.0"},{"method","textDocument/didClose"},
|
|
{"params",{{"textDocument",{{"uri","file:///api.go"}}}}}};
|
|
orc.handle(closeMsg);
|
|
C(!orc.workspaceIndex().contains("file:///api.go"), "doc closed");
|
|
P();
|
|
}
|
|
|
|
void t2(){
|
|
T(definition_routes_cross_language);
|
|
auto table = buildTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
json req = {{"jsonrpc","2.0"},{"id",1},{"method","textDocument/definition"},
|
|
{"params",{{"textDocument",{{"uri","file:///client.ts"}}},
|
|
{"symbol","HandleRequest"}}}};
|
|
auto resp = orc.handle(req);
|
|
C(resp.contains("result"), "has result");
|
|
C(!resp["result"].is_null(), "result not null");
|
|
C(resp["result"]["language"] == "Go", "provider language Go");
|
|
C(resp["result"]["provider"] == "api-server", "provider component");
|
|
P();
|
|
}
|
|
|
|
void t3(){
|
|
T(publishDiagnostics_and_getDiagnostics);
|
|
auto table = buildTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
ws::Diagnostic d1; d1.uri="file:///a.go"; d1.severity=1; d1.message="E1"; d1.source="gopls";
|
|
ws::Diagnostic d2; d2.uri="file:///a.go"; d2.severity=2; d2.message="W1"; d2.source="staticcheck";
|
|
orc.publishDiagnostics("gopls", "file:///a.go", {d1});
|
|
orc.publishDiagnostics("staticcheck", "file:///a.go", {d2});
|
|
auto diags = orc.getDiagnostics("file:///a.go");
|
|
C(diags.size() == 2, "two diagnostics");
|
|
int errors = 0;
|
|
for (auto& d : diags) if (d.severity == 1) ++errors;
|
|
C(errors == 1, "one error");
|
|
P();
|
|
}
|
|
|
|
void t4(){
|
|
T(registerCompletions_merged_deduped);
|
|
auto table = buildTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
orc.registerCompletions("gopls", { {"fmt.Println",3,"func","gopls"},
|
|
{"fmt.Printf", 3,"func","gopls"} });
|
|
orc.registerCompletions("tsserver", { {"console.log",3,"func","tsserver"},
|
|
{"fmt.Println",3,"func","tsserver"} }); // dup
|
|
auto items = orc.getCompletions();
|
|
C(items.size() == 3, "three unique items");
|
|
bool hasGoPrintln = false;
|
|
for (auto& i : items)
|
|
if (i.label == "fmt.Println") hasGoPrintln = true;
|
|
C(hasGoPrintln, "fmt.Println present");
|
|
P();
|
|
}
|
|
|
|
void t5(){
|
|
T(summary_reflects_state);
|
|
auto table = buildTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
// Open two docs
|
|
auto open = [&](const std::string& uri, const std::string& lang){
|
|
json m = {{"jsonrpc","2.0"},{"method","textDocument/didOpen"},
|
|
{"params",{{"textDocument",{{"uri",uri},{"languageId",lang},
|
|
{"text",""},{"version",1}}}}}};
|
|
orc.handle(m);
|
|
};
|
|
open("file:///x.go", "go");
|
|
open("file:///y.ts", "typescript");
|
|
ws::Diagnostic err; err.uri="file:///x.go"; err.severity=1; err.message="E"; err.source="gopls";
|
|
orc.publishDiagnostics("gopls", "file:///x.go", {err});
|
|
orc.registerCompletions("gopls", { {"alpha",1,"","gopls"}, {"beta",1,"","gopls"} });
|
|
auto s = orc.summary();
|
|
C(s.openDocuments == 2, "two open docs");
|
|
C(s.diagnosticUris == 1, "one uri with diagnostics");
|
|
C(s.completionItems == 2, "two completion items");
|
|
C(s.errorCount == 1, "one error");
|
|
P();
|
|
}
|
|
|
|
int main(){
|
|
std::cout << "Step 1916: LSPOrchestrator\n";
|
|
t1(); t2(); t3(); t4(); t5();
|
|
std::cout << "\n" << p << "/" << (p+f) << " passed\n";
|
|
return f > 0 ? 1 : 0;
|
|
}
|