- 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>
165 lines
6.6 KiB
C++
165 lines
6.6 KiB
C++
// Step 1917: Sprint 277 Integration
|
|
// poly-api project: Go HTTP server + TypeScript client.
|
|
// The orchestrator opens both files, handles cross-language goto-definition
|
|
// (TS client calls Go handler), aggregates diagnostics from both servers,
|
|
// and merges completions from both LSPs.
|
|
//
|
|
// t1: poly-api symbol table loads Go+TS boundary
|
|
// t2: orchestrator opens Go+TS docs via didOpen messages
|
|
// t3: TS→Go cross-language goto-definition resolves through orchestrator
|
|
// t4: diagnostics from gopls + tsserver aggregated per URI
|
|
// t5: completions merged from gopls + tsserver, deduped; summary accurate
|
|
// t6 (bonus): Sprint277IntegrationSummary reports complete
|
|
|
|
#include "LSPOrchestrator.h"
|
|
#include "ABIBoundaryExtractor.h"
|
|
#include "CrossLanguageSymbolTable.h"
|
|
#include "Sprint277IntegrationSummary.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-api: TypeScript client calls Go server via HTTP handler
|
|
static ws::CrossLanguageSymbolTable buildPolyApiTable() {
|
|
ws::CrossLanguageSymbolTable table;
|
|
|
|
ws::ABIBoundaryNode n1;
|
|
n1.name="HandleRequest"; n1.kind="function";
|
|
n1.fromComponent="api-client"; n1.toComponent="api-server";
|
|
n1.fromLanguage="TypeScript"; n1.toLanguage="Go";
|
|
n1.signature="void HandleRequest(const char* body, int len)";
|
|
table.insert(n1);
|
|
|
|
ws::ABIBoundaryNode n2;
|
|
n2.name="HandleHealthCheck"; n2.kind="function";
|
|
n2.fromComponent="api-client"; n2.toComponent="api-server";
|
|
n2.fromLanguage="TypeScript"; n2.toLanguage="Go";
|
|
n2.signature="void HandleHealthCheck()";
|
|
table.insert(n2);
|
|
|
|
return table;
|
|
}
|
|
|
|
static void openDoc(ws::LSPOrchestrator& orc, const std::string& uri,
|
|
const std::string& lang, const std::string& text) {
|
|
json m = {{"jsonrpc","2.0"},{"method","textDocument/didOpen"},
|
|
{"params",{{"textDocument",{{"uri",uri},{"languageId",lang},
|
|
{"text",text},{"version",1}}}}}};
|
|
orc.handle(m);
|
|
}
|
|
|
|
void t1(){
|
|
T(polyapi_symbol_table_loads);
|
|
auto table = buildPolyApiTable();
|
|
C(table.contains("TypeScript", "HandleRequest"), "TS has HandleRequest");
|
|
C(table.contains("Go", "HandleRequest"), "Go has HandleRequest");
|
|
C(table.contains("TypeScript", "HandleHealthCheck"), "TS has HandleHealthCheck");
|
|
P();
|
|
}
|
|
|
|
void t2(){
|
|
T(orchestrator_opens_go_and_ts_docs);
|
|
auto table = buildPolyApiTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
openDoc(orc, "file:///server/main.go", "go", "package main");
|
|
openDoc(orc, "file:///client/api.ts", "typescript", "export {}");
|
|
C(orc.workspaceIndex().size() == 2, "two docs open");
|
|
C(orc.workspaceIndex().getLanguage("file:///server/main.go") == "go", "go lang");
|
|
C(orc.workspaceIndex().getLanguage("file:///client/api.ts") == "typescript", "ts lang");
|
|
P();
|
|
}
|
|
|
|
void t3(){
|
|
T(ts_to_go_definition_resolves_through_orchestrator);
|
|
auto table = buildPolyApiTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
openDoc(orc, "file:///client/api.ts", "typescript", "HandleRequest();");
|
|
json req = {{"jsonrpc","2.0"},{"id",1},{"method","textDocument/definition"},
|
|
{"params",{{"textDocument",{{"uri","file:///client/api.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 api-server");
|
|
C(resp["result"]["scipSymbol"] == "api-server/HandleRequest.", "scip symbol");
|
|
P();
|
|
}
|
|
|
|
void t4(){
|
|
T(diagnostics_aggregated_from_gopls_and_tsserver);
|
|
auto table = buildPolyApiTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
openDoc(orc, "file:///server/main.go", "go", "package main");
|
|
openDoc(orc, "file:///client/api.ts", "typescript", "");
|
|
|
|
ws::Diagnostic goErr; goErr.uri="file:///server/main.go"; goErr.severity=1;
|
|
goErr.message="undefined: x"; goErr.source="gopls";
|
|
ws::Diagnostic tsWarn; tsWarn.uri="file:///client/api.ts"; tsWarn.severity=2;
|
|
tsWarn.message="implicit any"; tsWarn.source="tsserver";
|
|
ws::Diagnostic tsErr; tsErr.uri="file:///client/api.ts"; tsErr.severity=1;
|
|
tsErr.message="type mismatch"; tsErr.source="tsserver";
|
|
|
|
orc.publishDiagnostics("gopls", "file:///server/main.go", {goErr});
|
|
orc.publishDiagnostics("tsserver", "file:///client/api.ts", {tsWarn, tsErr});
|
|
|
|
auto goDiags = orc.getDiagnostics("file:///server/main.go");
|
|
C(goDiags.size() == 1, "one go diagnostic");
|
|
C(goDiags[0].severity == 1, "go diagnostic is error");
|
|
|
|
auto tsDiags = orc.getDiagnostics("file:///client/api.ts");
|
|
C(tsDiags.size() == 2, "two ts diagnostics");
|
|
P();
|
|
}
|
|
|
|
void t5(){
|
|
T(completions_merged_and_summary_accurate);
|
|
auto table = buildPolyApiTable();
|
|
ws::LSPOrchestrator orc(table);
|
|
openDoc(orc, "file:///server/main.go", "go", "package main");
|
|
openDoc(orc, "file:///client/api.ts", "typescript", "");
|
|
|
|
orc.registerCompletions("gopls", { {"fmt.Println",3,"func","gopls"},
|
|
{"HandleRequest",2,"func","gopls"} });
|
|
orc.registerCompletions("tsserver", { {"console.log",3,"func","tsserver"},
|
|
{"HandleRequest",2,"func","tsserver"} }); // dup
|
|
|
|
auto items = orc.getCompletions();
|
|
C(items.size() == 3, "three unique completions");
|
|
|
|
ws::Diagnostic err; err.uri="file:///server/main.go"; err.severity=1;
|
|
err.message="E1"; err.source="gopls";
|
|
orc.publishDiagnostics("gopls", "file:///server/main.go", {err});
|
|
|
|
auto s = orc.summary();
|
|
C(s.openDocuments == 2, "two open docs");
|
|
C(s.diagnosticUris == 1, "one uri with diagnostics");
|
|
C(s.completionItems == 3, "three completion items");
|
|
C(s.errorCount == 1, "one error");
|
|
P();
|
|
}
|
|
|
|
void t6(){
|
|
T(sprint277_integration_summary_complete);
|
|
ws::Sprint277IntegrationSummary s;
|
|
C(s.stepsCompleted == 5, "5 steps");
|
|
C(s.success, "success");
|
|
C(s.sprintName().find("277") != std::string::npos,"sprint 277 in name");
|
|
P();
|
|
}
|
|
|
|
int main(){
|
|
std::cout << "Step 1917: Sprint 277 Integration (poly-api: Go+TypeScript)\n";
|
|
t1(); t2(); t3(); t4(); t5(); t6();
|
|
std::cout << "\n" << p << "/" << (p+f) << " passed\n";
|
|
return f > 0 ? 1 : 0;
|
|
}
|