fix(lsp): parse-error survival, symbol ranges and UTF-16 positions

Assisted-by: GLM 5.3
This commit is contained in:
2026-09-19 23:49:19 +02:00
parent eb8b0cd316
commit b3908fc43d
4 changed files with 547 additions and 57 deletions
+44 -3
View File
@@ -6,6 +6,7 @@ package lsp
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
@@ -45,6 +46,11 @@ func (s *Server) Run() error {
if err == io.EOF {
return nil
}
if errors.Is(err, errMalformed) {
// The message was already answered with a -32700 response;
// one malformed frame must not end the session.
continue
}
if err != nil {
return err
}
@@ -54,9 +60,20 @@ func (s *Server) Run() error {
}
}
// read parses one Content-Length framed JSON-RPC message.
// errMalformed marks a message that could not be framed or decoded. The
// server answers it with a -32700 parse error before returning it, so the
// read loop continues instead of propagating a process exit.
var errMalformed = errors.New("malformed JSON-RPC message")
// read parses one Content-Length framed JSON-RPC message. A Content-Length
// value that does not parse, or a body that is not valid JSON, is answered
// with a -32700 response against the null id (as the JSON-RPC specification
// requires when the request itself cannot be decoded) and reported as
// errMalformed; the header block is consumed first so the next frame is
// read from its true start.
func (s *Server) read() (*rpcMessage, error) {
length := -1
var headerErr string
for {
line, err := s.in.ReadString('\n')
if err != nil {
@@ -67,9 +84,18 @@ func (s *Server) read() (*rpcMessage, error) {
break
}
if k, v, ok := strings.Cut(line, ":"); ok && strings.EqualFold(strings.TrimSpace(k), "Content-Length") {
length, _ = strconv.Atoi(strings.TrimSpace(v))
n, err := strconv.Atoi(strings.TrimSpace(v))
if err != nil || n < 0 {
headerErr = "invalid Content-Length header: " + strconv.Quote(strings.TrimSpace(v))
continue
}
length = n
}
}
if headerErr != "" {
s.parseError(headerErr)
return nil, errMalformed
}
if length < 0 {
return nil, fmt.Errorf("missing Content-Length header")
}
@@ -79,7 +105,8 @@ func (s *Server) read() (*rpcMessage, error) {
}
var msg rpcMessage
if err := json.Unmarshal(body, &msg); err != nil {
return nil, err
s.parseError("invalid JSON body: " + err.Error())
return nil, errMalformed
}
return &msg, nil
}
@@ -101,6 +128,13 @@ func (s *Server) send(msg *rpcMessage) error {
}
func (s *Server) respond(id *json.RawMessage, result any) {
// JSON-RPC 2.0 requires the result member to be present even when its
// value is null (shutdown, a hover over nothing); a nil result is
// marshalled as an explicit null rather than dropped by omitempty.
// Notifications never take this path, so they grow no member.
if result == nil {
result = json.RawMessage("null")
}
_ = s.send(&rpcMessage{ID: id, Result: result})
}
@@ -108,6 +142,13 @@ func (s *Server) respondError(id *json.RawMessage, code int, msg string) {
_ = s.send(&rpcMessage{ID: id, Error: &rpcError{Code: code, Message: msg}})
}
// parseError answers a -32700 parse error against the null id: when the
// request could not be decoded there is no id to echo back.
func (s *Server) parseError(msg string) {
id := json.RawMessage("null")
s.respondError(&id, errParse, msg)
}
func (s *Server) notify(method string, params any) {
raw, _ := json.Marshal(params)
_ = s.send(&rpcMessage{Method: method, Params: raw})