From 515a0fa6d497c123b47e1ca50f4c4da7f618fbd3 Mon Sep 17 00:00:00 2001 From: skewb1k Date: Thu, 7 Aug 2025 20:57:58 +0300 Subject: [PATCH 1/4] fix: make ResponseMessage.Result omitzero According to the JSON-RPC specification, a response object must include either a `result` field or an `error` field, but not both. Without omitzero, if `Result` is nil, it is marshalled as 'null', causing both `result` and `error` fields to appear in the JSON object when `Error` is also present, which violates the specification. --- internal/lsp/lsproto/jsonrpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/lsp/lsproto/jsonrpc.go b/internal/lsp/lsproto/jsonrpc.go index 727b09e14f..d78894e824 100644 --- a/internal/lsp/lsproto/jsonrpc.go +++ b/internal/lsp/lsproto/jsonrpc.go @@ -207,7 +207,7 @@ func (r *RequestMessage) UnmarshalJSON(data []byte) error { type ResponseMessage struct { JSONRPC JSONRPCVersion `json:"jsonrpc"` ID *ID `json:"id,omitzero"` - Result any `json:"result"` + Result any `json:"result,omitzero"` Error *ResponseError `json:"error,omitzero"` } From 8e88d5b2918b05152c40e993a5dba1bd7834816f Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:21:16 -0700 Subject: [PATCH 2/4] Use explicit null for LSP responses --- internal/lsp/lsproto/_generate/generate.mts | 12 ++++++++--- internal/lsp/lsproto/lsp.go | 13 ++++++++++++ internal/lsp/lsproto/lsp_generated.go | 22 ++++++++++----------- internal/lsp/server.go | 2 +- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/internal/lsp/lsproto/_generate/generate.mts b/internal/lsp/lsproto/_generate/generate.mts index 9407b68ae2..581ebafc8f 100644 --- a/internal/lsp/lsproto/_generate/generate.mts +++ b/internal/lsp/lsproto/_generate/generate.mts @@ -704,10 +704,16 @@ function generateCode() { } writeLine(`// Response type for \`${request.method}\``); - const resultType = resolveType(request.result); - const goType = resultType.needsPointer ? `*${resultType.name}` : resultType.name; - writeLine(`type ${responseTypeName} = ${goType}`); + // Special case for response types that are explicitly base type "null" + if (request.result.kind === "base" && request.result.name === "null") { + writeLine(`type ${responseTypeName} = Null`); + } + else { + const resultType = resolveType(request.result); + const goType = resultType.needsPointer ? `*${resultType.name}` : resultType.name; + writeLine(`type ${responseTypeName} = ${goType}`); + } writeLine(""); } diff --git a/internal/lsp/lsproto/lsp.go b/internal/lsp/lsproto/lsp.go index 6dada4b921..55b105931b 100644 --- a/internal/lsp/lsproto/lsp.go +++ b/internal/lsp/lsproto/lsp.go @@ -83,3 +83,16 @@ type NotificationInfo[Params any] struct { _ [0]Params Method Method } + +type Null struct{} + +func (Null) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + if k := dec.PeekKind(); k != 'n' { + return fmt.Errorf("expected null, got %s", k) + } + return dec.SkipValue() +} + +func (Null) MarshalJSONTo(enc *jsontext.Encoder) error { + return enc.WriteToken(jsontext.Null) +} diff --git a/internal/lsp/lsproto/lsp_generated.go b/internal/lsp/lsproto/lsp_generated.go index e39291134f..eb993ea513 100644 --- a/internal/lsp/lsproto/lsp_generated.go +++ b/internal/lsp/lsproto/lsp_generated.go @@ -20993,7 +20993,7 @@ type FoldingRangeResponse = FoldingRangesOrNull var TextDocumentFoldingRangeInfo = RequestInfo[*FoldingRangeParams, FoldingRangeResponse]{Method: MethodTextDocumentFoldingRange} // Response type for `workspace/foldingRange/refresh` -type FoldingRangeRefreshResponse = any +type FoldingRangeRefreshResponse = Null // Type mapping info for `workspace/foldingRange/refresh` var WorkspaceFoldingRangeRefreshInfo = RequestInfo[any, FoldingRangeRefreshResponse]{Method: MethodWorkspaceFoldingRangeRefresh} @@ -21011,7 +21011,7 @@ type SelectionRangeResponse = SelectionRangesOrNull var TextDocumentSelectionRangeInfo = RequestInfo[*SelectionRangeParams, SelectionRangeResponse]{Method: MethodTextDocumentSelectionRange} // Response type for `window/workDoneProgress/create` -type WorkDoneProgressCreateResponse = any +type WorkDoneProgressCreateResponse = Null // Type mapping info for `window/workDoneProgress/create` var WindowWorkDoneProgressCreateInfo = RequestInfo[*WorkDoneProgressCreateParams, WorkDoneProgressCreateResponse]{Method: MethodWindowWorkDoneProgressCreate} @@ -21053,7 +21053,7 @@ type SemanticTokensRangeResponse = SemanticTokensOrNull var TextDocumentSemanticTokensRangeInfo = RequestInfo[*SemanticTokensRangeParams, SemanticTokensRangeResponse]{Method: MethodTextDocumentSemanticTokensRange} // Response type for `workspace/semanticTokens/refresh` -type SemanticTokensRefreshResponse = any +type SemanticTokensRefreshResponse = Null // Type mapping info for `workspace/semanticTokens/refresh` var WorkspaceSemanticTokensRefreshInfo = RequestInfo[any, SemanticTokensRefreshResponse]{Method: MethodWorkspaceSemanticTokensRefresh} @@ -21119,7 +21119,7 @@ type InlineValueResponse = InlineValuesOrNull var TextDocumentInlineValueInfo = RequestInfo[*InlineValueParams, InlineValueResponse]{Method: MethodTextDocumentInlineValue} // Response type for `workspace/inlineValue/refresh` -type InlineValueRefreshResponse = any +type InlineValueRefreshResponse = Null // Type mapping info for `workspace/inlineValue/refresh` var WorkspaceInlineValueRefreshInfo = RequestInfo[any, InlineValueRefreshResponse]{Method: MethodWorkspaceInlineValueRefresh} @@ -21137,7 +21137,7 @@ type InlayHintResolveResponse = *InlayHint var InlayHintResolveInfo = RequestInfo[*InlayHint, InlayHintResolveResponse]{Method: MethodInlayHintResolve} // Response type for `workspace/inlayHint/refresh` -type InlayHintRefreshResponse = any +type InlayHintRefreshResponse = Null // Type mapping info for `workspace/inlayHint/refresh` var WorkspaceInlayHintRefreshInfo = RequestInfo[any, InlayHintRefreshResponse]{Method: MethodWorkspaceInlayHintRefresh} @@ -21155,7 +21155,7 @@ type WorkspaceDiagnosticResponse = *WorkspaceDiagnosticReport var WorkspaceDiagnosticInfo = RequestInfo[*WorkspaceDiagnosticParams, WorkspaceDiagnosticResponse]{Method: MethodWorkspaceDiagnostic} // Response type for `workspace/diagnostic/refresh` -type DiagnosticRefreshResponse = any +type DiagnosticRefreshResponse = Null // Type mapping info for `workspace/diagnostic/refresh` var WorkspaceDiagnosticRefreshInfo = RequestInfo[any, DiagnosticRefreshResponse]{Method: MethodWorkspaceDiagnosticRefresh} @@ -21173,19 +21173,19 @@ type TextDocumentContentResponse = *TextDocumentContentResult var WorkspaceTextDocumentContentInfo = RequestInfo[*TextDocumentContentParams, TextDocumentContentResponse]{Method: MethodWorkspaceTextDocumentContent} // Response type for `workspace/textDocumentContent/refresh` -type TextDocumentContentRefreshResponse = any +type TextDocumentContentRefreshResponse = Null // Type mapping info for `workspace/textDocumentContent/refresh` var WorkspaceTextDocumentContentRefreshInfo = RequestInfo[*TextDocumentContentRefreshParams, TextDocumentContentRefreshResponse]{Method: MethodWorkspaceTextDocumentContentRefresh} // Response type for `client/registerCapability` -type RegistrationResponse = any +type RegistrationResponse = Null // Type mapping info for `client/registerCapability` var ClientRegisterCapabilityInfo = RequestInfo[*RegistrationParams, RegistrationResponse]{Method: MethodClientRegisterCapability} // Response type for `client/unregisterCapability` -type UnregistrationResponse = any +type UnregistrationResponse = Null // Type mapping info for `client/unregisterCapability` var ClientUnregisterCapabilityInfo = RequestInfo[*UnregistrationParams, UnregistrationResponse]{Method: MethodClientUnregisterCapability} @@ -21197,7 +21197,7 @@ type InitializeResponse = *InitializeResult var InitializeInfo = RequestInfo[*InitializeParams, InitializeResponse]{Method: MethodInitialize} // Response type for `shutdown` -type ShutdownResponse = any +type ShutdownResponse = Null // Type mapping info for `shutdown` var ShutdownInfo = RequestInfo[any, ShutdownResponse]{Method: MethodShutdown} @@ -21299,7 +21299,7 @@ type CodeLensResolveResponse = *CodeLens var CodeLensResolveInfo = RequestInfo[*CodeLens, CodeLensResolveResponse]{Method: MethodCodeLensResolve} // Response type for `workspace/codeLens/refresh` -type CodeLensRefreshResponse = any +type CodeLensRefreshResponse = Null // Type mapping info for `workspace/codeLens/refresh` var WorkspaceCodeLensRefreshInfo = RequestInfo[any, CodeLensRefreshResponse]{Method: MethodWorkspaceCodeLensRefresh} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 778dc0a41c..baf7296b66 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -660,7 +660,7 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali func (s *Server) handleShutdown(ctx context.Context, params any) (lsproto.ShutdownResponse, error) { s.projectService.Close() - return nil, nil + return lsproto.ShutdownResponse{}, nil } func (s *Server) handleExit(ctx context.Context, params any) error { From 20959a21f29a37463ccc5778861655f46b2b9198 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:45:04 -0700 Subject: [PATCH 3/4] Do TokenRead --- internal/lsp/lsproto/lsp.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/lsp/lsproto/lsp.go b/internal/lsp/lsproto/lsp.go index 55b105931b..f8100a667e 100644 --- a/internal/lsp/lsproto/lsp.go +++ b/internal/lsp/lsproto/lsp.go @@ -90,7 +90,8 @@ func (Null) UnmarshalJSONFrom(dec *jsontext.Decoder) error { if k := dec.PeekKind(); k != 'n' { return fmt.Errorf("expected null, got %s", k) } - return dec.SkipValue() + _, err := dec.ReadToken() + return err } func (Null) MarshalJSONTo(enc *jsontext.Encoder) error { From f53a10635bfa6ccc6a52d79ec0a402f45430f6bf Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:49:54 -0700 Subject: [PATCH 4/4] Make consistent with other null checks --- internal/lsp/lsproto/lsp.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/lsp/lsproto/lsp.go b/internal/lsp/lsproto/lsp.go index f8100a667e..f8e8c5defc 100644 --- a/internal/lsp/lsproto/lsp.go +++ b/internal/lsp/lsproto/lsp.go @@ -87,11 +87,14 @@ type NotificationInfo[Params any] struct { type Null struct{} func (Null) UnmarshalJSONFrom(dec *jsontext.Decoder) error { - if k := dec.PeekKind(); k != 'n' { - return fmt.Errorf("expected null, got %s", k) + data, err := dec.ReadValue() + if err != nil { + return err } - _, err := dec.ReadToken() - return err + if string(data) != "null" { + return fmt.Errorf("expected null, got %s", data) + } + return nil } func (Null) MarshalJSONTo(enc *jsontext.Encoder) error {