fix(lsp): parse-error survival, symbol ranges and UTF-16 positions
Assisted-by: GLM 5.3
This commit is contained in:
+44
-3
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user