diff --git a/CHANGELOG.md b/CHANGELOG.md index 0205b3f..2e025e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **ServerCancelled retry** — `LspClient::request()` now retries up to 3 times with exponential backoff (500 ms → 1 s → 2 s) when an LSP server returns error code -32802 with `data.retriggerRequest: true`, instead of propagating the error immediately to the MCP caller (#128) - **Integration test readiness gate** — Replaced `publishDiagnostics`-based readiness signal with hover-probe polling (3 consecutive successful hover responses required), matching the ra_e2e approach; fixes 3 of 5 integration tests that failed consistently in isolation after PR #123 (#127) - **LSP server requests** — Handle server-to-client requests such as `client/registerCapability`, fixing tsgo timeouts. +- **Pull diagnostics** — Omit absent `identifier` and `previousResultId` fields from `textDocument/diagnostic` requests, fixing tsgo `InvalidParams` errors. - **Integration tests** — Add `[workspace]` table to `tests/fixtures/rust_workspace/Cargo.toml` so cargo treats the fixture as a standalone workspace; fixes 8 rust-analyzer integration tests that failed with "Failed to load workspaces." (#118) - **e2e coverage** — Add ra_e2e sub-cases for `get_signature_help`, `go_to_implementation`, `go_to_type_definition`, `get_inlay_hints` (4 LSP 3.17 tools from #124 had no coverage); add `list_resources`, `read_resource`, `subscribe_resource`, `unsubscribe_resource` to `McpClient` and ra_e2e_suite (MCP resources path was entirely untested) (#129, #130) diff --git a/crates/mcpls-core/src/bridge/translator.rs b/crates/mcpls-core/src/bridge/translator.rs index 9f78b1c..e0a6de7 100644 --- a/crates/mcpls-core/src/bridge/translator.rs +++ b/crates/mcpls-core/src/bridge/translator.rs @@ -116,6 +116,30 @@ impl Default for Translator { } } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct DiagnosticRequestParams { + text_document: TextDocumentIdentifier, + #[serde(skip_serializing_if = "Option::is_none")] + identifier: Option, + #[serde(skip_serializing_if = "Option::is_none")] + previous_result_id: Option, + #[serde(flatten)] + work_done_progress_params: WorkDoneProgressParams, + #[serde(flatten)] + partial_result_params: PartialResultParams, +} + +fn diagnostic_request_params(text_document: TextDocumentIdentifier) -> DiagnosticRequestParams { + DiagnosticRequestParams { + text_document, + identifier: None, + previous_result_id: None, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + } +} + /// Position in a document (1-based for MCP). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Position2D { @@ -750,13 +774,7 @@ impl Translator { .ensure_open(&validated_path, &client) .await?; - let params = lsp_types::DocumentDiagnosticParams { - text_document: TextDocumentIdentifier { uri }, - identifier: None, - previous_result_id: None, - work_done_progress_params: WorkDoneProgressParams::default(), - partial_result_params: PartialResultParams::default(), - }; + let params = diagnostic_request_params(TextDocumentIdentifier { uri }); let timeout_duration = Duration::from_secs(30); let response: lsp_types::DocumentDiagnosticReportResult = client @@ -2074,6 +2092,17 @@ mod tests { // This test verifies the data structure is properly initialized. } + #[test] + fn test_diagnostic_request_params_omit_optional_null_fields() { + let uri = "file:///test.ts".parse().unwrap(); + let params = diagnostic_request_params(TextDocumentIdentifier { uri }); + let value = serde_json::to_value(params).unwrap(); + + assert_eq!(value["textDocument"]["uri"], "file:///test.ts"); + assert!(value.get("identifier").is_none()); + assert!(value.get("previousResultId").is_none()); + } + #[test] fn test_validate_path_no_workspace_roots() { let translator = Translator::new();