vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,2 @@
metaModel.json
metaModel.schema.json

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env -S node --experimental-strip-types
// Usage: node --experimental-strip-types fetchModel.mts
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
const __filename = url.fileURLToPath(new URL(import.meta.url));
const __dirname = path.dirname(__filename);
const metaModelPath = path.join(__dirname, "metaModel.json");
const metaModelSchemaPath = path.join(__dirname, "metaModelSchema.mts");
// Resolve the vscode-languageclient version from the root package-lock.json.
const lockfilePath = path.resolve(__dirname, "../../../../package-lock.json");
const lockfile = JSON.parse(fs.readFileSync(lockfilePath, "utf-8"));
const clientVersion: string = lockfile.packages["node_modules/vscode-languageclient"].version;
const ref = `release/client/${clientVersion}`;
console.log(`Using vscode-languageclient@${clientVersion}`);
const metaModelURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${ref}/protocol/metaModel.json`;
const metaModelSchemaURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${ref}/tools/src/metaModel.ts`;
const metaModelResponse = await fetch(metaModelURL);
const metaModel = await metaModelResponse.text();
fs.writeFileSync(metaModelPath, metaModel);
const metaModelSchemaResponse = await fetch(metaModelSchemaURL);
let metaModelSchema = await metaModelSchemaResponse.text();
// Patch the schema to add omitzeroValue property to Property type
metaModelSchema = metaModelSchema.replace(
/(\t \* Whether the property is deprecated or not\. If deprecated\n\t \* the property contains the deprecation message\.\n\t \*\/\n\tdeprecated\?: string;)\n}/m,
`$1\n\n\t/**\n\t * Whether this property uses omitzero without being a pointer.\n\t * Custom extension for special value types.\n\t */\n\tomitzeroValue?: boolean;\n}`,
);
fs.writeFileSync(metaModelSchemaPath, metaModelSchema);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,635 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
export type BaseTypes = 'URI' | 'DocumentUri' | 'integer' | 'uinteger' | 'decimal' | 'RegExp' | 'string' | 'boolean' | 'null';
export type TypeKind = 'base' | 'reference' | 'array' | 'map' | 'and' | 'or' | 'tuple' | 'literal' | 'stringLiteral' | 'integerLiteral' | 'booleanLiteral';
/**
* Indicates in which direction a message is sent in the protocol.
*/
export type MessageDirection = 'clientToServer' | 'serverToClient' | 'both';
/**
* Represents a base type like `string` or `DocumentUri`.
*/
export type BaseType = {
kind: 'base';
name: BaseTypes;
};
/**
* Represents a reference to another type (e.g. `TextDocument`).
* This is either a `Structure`, a `Enumeration` or a `TypeAlias`
* in the same meta model.
*/
export type ReferenceType = {
kind: 'reference';
name: string;
};
/**
* Represents an array type (e.g. `TextDocument[]`).
*/
export type ArrayType = {
kind: 'array';
element: Type;
};
/**
* Represents a type that can be used as a key in a
* map type. If a reference type is used then the
* type must either resolve to a `string` or `integer`
* type. (e.g. `type ChangeAnnotationIdentifier === string`).
*/
export type MapKeyType = { kind: 'base'; name: 'URI' | 'DocumentUri' | 'string' | 'integer' } | ReferenceType;
/**
* Represents a JSON object map
* (e.g. `interface Map<K extends string | integer, V> { [key: K] => V; }`).
*/
export type MapType = {
kind: 'map';
key: MapKeyType;
value: Type;
};
/**
* Represents an `and` type
* (e.g. TextDocumentParams & WorkDoneProgressParams`).
*/
export type AndType = {
kind: 'and';
items: Type[];
};
/**
* Represents an `or` type
* (e.g. `Location | LocationLink`).
*/
export type OrType = {
kind: 'or';
items: Type[];
};
/**
* Represents a `tuple` type
* (e.g. `[integer, integer]`).
*/
export type TupleType = {
kind: 'tuple';
items: Type[];
};
/**
* Represents a literal structure
* (e.g. `property: { start: uinteger; end: uinteger; }`).
*/
export type StructureLiteralType = {
kind: 'literal';
value: StructureLiteral;
};
/**
* Represents a string literal type
* (e.g. `kind: 'rename'`).
*/
export type StringLiteralType = {
kind: 'stringLiteral';
value: string;
};
export type IntegerLiteralType = {
/**
* Represents an integer literal type
* (e.g. `kind: 1`).
*/
kind: 'integerLiteral';
value: number;
};
/**
* Represents a boolean literal type
* (e.g. `kind: true`).
*/
export type BooleanLiteralType = {
kind: 'booleanLiteral';
value: boolean;
};
export type Type = BaseType | ReferenceType | ArrayType | MapType | AndType | OrType | TupleType | StructureLiteralType | StringLiteralType | IntegerLiteralType | BooleanLiteralType;
/**
* Represents a LSP request
*/
export type Request = {
/**
* The request's method name.
*/
method: string;
/**
* The type name of the request if any.
*/
typeName?: string;
/**
* The parameter type(s) if any.
*/
params?: Type | Type[];
/**
* The result type.
*/
result: Type;
/**
* Optional partial result type if the request
* supports partial result reporting.
*/
partialResult?: Type;
/**
* An optional error data type.
*/
errorData?: Type;
/**
* Optional a dynamic registration method if it
* different from the request's method.
*/
registrationMethod?: string;
/**
* Optional registration options if the request
* supports dynamic registration.
*/
registrationOptions?: Type;
/**
* The direction in which this request is sent
* in the protocol.
*/
messageDirection: MessageDirection;
/**
* An optional documentation;
*/
documentation?: string;
/**
* Since when (release number) this request is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed feature. If omitted
* the feature is final.
*/
proposed?: boolean;
/**
* Whether the request is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
/**
* The client capability property path if any.
*/
clientCapability?: string;
/**
* The server capability property path if any.
*/
serverCapability?: string;
};
/**
* Represents a LSP notification
*/
export type Notification = {
/**
* The notifications's method name.
*/
method: string;
/**
* The type name of the notifications if any.
*/
typeName?: string;
/**
* The parameter type(s) if any.
*/
params?: Type | Type[];
/**
* Optional a dynamic registration method if it
* different from the notifications's method.
*/
registrationMethod?: string;
/**
* Optional registration options if the notification
* supports dynamic registration.
*/
registrationOptions?: Type;
/**
* The direction in which this notification is sent
* in the protocol.
*/
messageDirection: MessageDirection;
/**
* An optional documentation;
*/
documentation?: string;
/**
* Since when (release number) this notification is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed notification. If omitted
* the notification is final.
*/
proposed?: boolean;
/**
* Whether the notification is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
/**
* The client capability property path if any.
*/
clientCapability?: string;
/**
* The server capability property path if any.
*/
serverCapability?: string;
};
/**
* Represents an object property.
*/
export type Property = {
/**
* The property name;
*/
name: string;
/**
* The type of the property
*/
type: Type;
/**
* Whether the property is optional. If
* omitted, the property is mandatory.
*/
optional?: boolean;
/**
* An optional documentation.
*/
documentation?: string;
/**
* Since when (release number) this property is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed property. If omitted,
* the structure is final.
*/
proposed?: boolean;
/**
* Whether the property is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
/**
* Whether this property uses omitzero without being a pointer.
* Custom extension for special value types.
*/
omitzeroValue?: boolean;
};
/**
* Defines the structure of an object literal.
*/
export type Structure = {
/**
* The name of the structure.
*/
name: string;
/**
* Structures extended from. This structures form
* a polymorphic type hierarchy.
*/
extends?: Type[];
/**
* Structures to mix in. The properties of these
* structures are `copied` into this structure.
* Mixins don't form a polymorphic type hierarchy in
* LSP.
*/
mixins?: Type[];
/**
* The properties.
*/
properties: Property[];
/**
* An optional documentation;
*/
documentation?: string;
/**
* Since when (release number) this structure is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed structure. If omitted,
* the structure is final.
*/
proposed?: boolean;
/**
* Whether the structure is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
};
/**
* Defines an unnamed structure of an object literal.
*/
export type StructureLiteral = {
/**
* The properties.
*/
properties: Property[];
/**
* An optional documentation.
*/
documentation?: string;
/**
* Since when (release number) this structure is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed structure. If omitted,
* the structure is final.
*/
proposed?: boolean;
/**
* Whether the literal is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
};
/**
* Defines a type alias.
* (e.g. `type Definition = Location | LocationLink`)
*/
export type TypeAlias = {
/**
* The name of the type alias.
*/
name: string;
/**
* The aliased type.
*/
type: Type;
/**
* An optional documentation.
*/
documentation?: string;
/**
* Since when (release number) this structure is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed type alias. If omitted,
* the type alias is final.
*/
proposed?: boolean;
/**
* Whether the type alias is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
};
/**
* Defines an enumeration entry.
*/
export type EnumerationEntry = {
/**
* The name of the enum item.
*/
name: string;
/**
* The value.
*/
value: string | number;
/**
* An optional documentation.
*/
documentation?: string;
/**
* Since when (release number) this enumeration entry is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed enumeration entry. If omitted,
* the enumeration entry is final.
*/
proposed?: boolean;
/**
* Whether the enum entry is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
};
export type EnumerationType = { kind: 'base'; name: 'string' | 'integer' | 'uinteger' };
/**
* Defines an enumeration.
*/
export type Enumeration = {
/**
* The name of the enumeration.
*/
name: string;
/**
* The type of the elements.
*/
type: EnumerationType;
/**
* The enum values.
*/
values: EnumerationEntry[];
/**
* Whether the enumeration supports custom values (e.g. values which are not
* part of the set defined in `values`). If omitted no custom values are
* supported.
*/
supportsCustomValues?: boolean;
/**
* An optional documentation.
*/
documentation?: string;
/**
* Since when (release number) this enumeration is
* available. Is undefined if not known.
*/
since?: string;
/**
* All since tags in case there was more than one tag.
* Is undefined if not known.
*/
sinceTags?: string[];
/**
* Whether this is a proposed enumeration. If omitted,
* the enumeration is final.
*/
proposed?: boolean;
/**
* Whether the enumeration is deprecated or not. If deprecated
* the property contains the deprecation message.
*/
deprecated?: string;
};
export type MetaData = {
/**
* The protocol version.
*/
version: string;
};
/**
* The actual meta model.
*/
export type MetaModel = {
/**
* Additional meta data.
*/
metaData: MetaData;
/**
* The requests.
*/
requests: Request[];
/**
* The notifications.
*/
notifications: Notification[];
/**
* The structures.
*/
structures: Structure[];
/**
* The enumerations.
*/
enumerations: Enumeration[];
/**
* The type aliases.
*/
typeAliases: TypeAlias[];
};

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"allowJs": true,
"checkJs": true,
"strict": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"types": ["node"],
"noUnusedLocals": true,
"noUnusedParameters": true,
},
"include": [
"*.mts",
"*.mjs"
]
}

View File

@@ -0,0 +1,33 @@
package lsproto
import (
"io"
"github.com/microsoft/typescript-go/internal/jsonrpc"
)
// https://microsoft.github.io/language-server-protocol/specifications/base/0.9/specification/
// BaseReader wraps jsonrpc.Reader for backwards compatibility.
type BaseReader struct {
*jsonrpc.Reader
}
// NewBaseReader creates a new BaseReader.
func NewBaseReader(r io.Reader) *BaseReader {
return &BaseReader{
Reader: jsonrpc.NewReader(r),
}
}
// BaseWriter wraps jsonrpc.Writer for backwards compatibility.
type BaseWriter struct {
*jsonrpc.Writer
}
// NewBaseWriter creates a new BaseWriter.
func NewBaseWriter(w io.Writer) *BaseWriter {
return &BaseWriter{
Writer: jsonrpc.NewWriter(w),
}
}

View File

@@ -0,0 +1,151 @@
package lsproto_test
import (
"bytes"
"errors"
"testing"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"gotest.tools/v3/assert"
)
func TestBaseReader(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []byte
value []byte
err string
}{
{
name: "empty",
input: []byte("Content-Length: 0\r\n\r\n"),
err: "jsonrpc: no content length",
},
{
name: "early end",
input: []byte("oops"),
err: "EOF",
},
{
name: "negative length",
input: []byte("Content-Length: -1\r\n\r\n"),
err: "jsonrpc: invalid content length: negative value -1",
},
{
name: "invalid content",
input: []byte("Content-Length: 1\r\n\r\n{"),
value: []byte("{"),
},
{
name: "valid content",
input: []byte("Content-Length: 2\r\n\r\n{}"),
value: []byte("{}"),
},
{
name: "extra header values",
input: []byte("Content-Length: 2\r\nExtra: 1\r\n\r\n{}"),
value: []byte("{}"),
},
{
name: "too long content length",
input: []byte("Content-Length: 100\r\n\r\n{}"),
err: "jsonrpc: read content: unexpected EOF",
},
{
name: "missing content length",
input: []byte("Content-Length: \r\n\r\n{}"),
err: "jsonrpc: invalid content length: parse error: strconv.ParseInt: parsing \"\": invalid syntax",
},
{
name: "invalid header",
input: []byte("Nope\r\n\r\n{}"),
err: "jsonrpc: invalid header: \"Nope\\r\\n\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := lsproto.NewBaseReader(bytes.NewReader(tt.input))
out, err := r.Read()
if tt.err != "" {
assert.Error(t, err, tt.err)
}
assert.DeepEqual(t, out, tt.value)
})
}
}
func TestBaseReaderMultipleReads(t *testing.T) {
t.Parallel()
data := []byte(
"Content-Length: 4\r\n\r\n1234" +
"Content-Length: 2\r\n\r\n{}",
)
r := lsproto.NewBaseReader(bytes.NewReader(data))
v1, err := r.Read()
assert.NilError(t, err)
assert.DeepEqual(t, v1, []byte("1234"))
v2, err := r.Read()
assert.NilError(t, err)
assert.DeepEqual(t, v2, []byte("{}"))
_, err = r.Read()
assert.Error(t, err, "EOF")
}
type errorReader struct{}
func (*errorReader) Read([]byte) (int, error) {
return 0, errors.New("test error")
}
func TestBaseWriter(t *testing.T) {
t.Parallel()
tests := []struct {
name string
value []byte
input []byte
}{
{
name: "empty",
value: []byte("{}"),
input: []byte("Content-Length: 2\r\n\r\n{}"),
},
{
name: "bigger object",
value: []byte("{\"key\":\"value\"}"),
input: []byte("Content-Length: 15\r\n\r\n{\"key\":\"value\"}"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var b bytes.Buffer
w := lsproto.NewBaseWriter(&b)
err := w.Write(tt.value)
assert.NilError(t, err)
assert.DeepEqual(t, b.Bytes(), tt.input)
})
}
}
func TestBaseWriterWriteError(t *testing.T) {
t.Parallel()
w := lsproto.NewBaseWriter(&errorWriter{})
err := w.Write([]byte("{}"))
assert.Error(t, err, "test error")
}
type errorWriter struct{}
func (*errorWriter) Write([]byte) (int, error) {
return 0, errors.New("test error")
}

View File

@@ -0,0 +1,130 @@
package lsproto
import (
"fmt"
"github.com/microsoft/typescript-go/internal/json"
"github.com/microsoft/typescript-go/internal/jsonrpc"
)
// NewID creates an ID from an IntegerOrString value.
// This wrapper exists because lsproto has its own IntegerOrString type.
func NewID(rawValue IntegerOrString) *jsonrpc.ID {
if rawValue.String != nil {
return jsonrpc.NewIDString(*rawValue.String)
}
return jsonrpc.NewIDInt(*rawValue.Integer)
}
type Message struct {
Kind jsonrpc.MessageKind
msg any
}
func (m *Message) AsRequest() *RequestMessage {
return m.msg.(*RequestMessage)
}
func (m *Message) AsResponse() *ResponseMessage {
return m.msg.(*ResponseMessage)
}
func (m *Message) UnmarshalJSON(data []byte) error {
var raw struct {
JSONRPC jsonrpc.JSONRPCVersion `json:"jsonrpc"`
Method Method `json:"method"`
ID *jsonrpc.ID `json:"id,omitzero"`
Params json.Value `json:"params"`
// We don't have a method in the response, so we have no idea what to decode.
// Store the raw text and let the caller decode it.
Result json.Value `json:"result,omitzero"`
Error *jsonrpc.ResponseError `json:"error,omitzero"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return fmt.Errorf("%w: %w", ErrorCodeInvalidRequest, err)
}
if raw.ID != nil && raw.Method == "" {
m.Kind = jsonrpc.MessageKindResponse
m.msg = &ResponseMessage{
ID: raw.ID,
Result: raw.Result,
Error: raw.Error,
}
return nil
}
var params any
if len(raw.Params) > 0 {
params = raw.Params
}
if raw.ID == nil {
m.Kind = jsonrpc.MessageKindNotification
} else {
m.Kind = jsonrpc.MessageKindRequest
}
m.msg = &RequestMessage{
ID: raw.ID,
Method: raw.Method,
Params: params,
}
return nil
}
func (m *Message) MarshalJSON() ([]byte, error) {
return json.Marshal(m.msg)
}
type RequestMessage struct {
JSONRPC jsonrpc.JSONRPCVersion `json:"jsonrpc"`
ID *jsonrpc.ID `json:"id,omitzero"`
Method Method `json:"method"`
Params any `json:"params,omitzero"`
}
func (r *RequestMessage) Message() *Message {
kind := jsonrpc.MessageKindRequest
if r.ID == nil {
kind = jsonrpc.MessageKindNotification
}
return &Message{
Kind: kind,
msg: r,
}
}
func (r *RequestMessage) UnmarshalJSON(data []byte) error {
var raw struct {
JSONRPC jsonrpc.JSONRPCVersion `json:"jsonrpc"`
ID *jsonrpc.ID `json:"id"`
Method Method `json:"method"`
Params json.Value `json:"params"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return fmt.Errorf("%w: %w", ErrorCodeInvalidRequest, err)
}
r.ID = raw.ID
r.Method = raw.Method
if len(raw.Params) > 0 {
r.Params = raw.Params
}
return nil
}
type ResponseMessage struct {
JSONRPC jsonrpc.JSONRPCVersion `json:"jsonrpc"`
ID *jsonrpc.ID `json:"id"`
Result any `json:"result,omitzero"`
Error *jsonrpc.ResponseError `json:"error,omitzero"`
}
func (r *ResponseMessage) Message() *Message {
return &Message{
Kind: jsonrpc.MessageKindResponse,
msg: r,
}
}

View File

@@ -0,0 +1,312 @@
package lsproto
import (
"bytes"
"context"
"fmt"
"net/url"
"strings"
"github.com/microsoft/typescript-go/internal/bundled"
"github.com/microsoft/typescript-go/internal/json"
"github.com/microsoft/typescript-go/internal/jsonrpc"
"github.com/microsoft/typescript-go/internal/tspath"
)
type DocumentUri string // !!!
func (uri DocumentUri) FileName() string {
if bundled.IsBundled(string(uri)) {
return string(uri)
}
if strings.HasPrefix(string(uri), "file://") {
parsed, err := url.Parse(string(uri))
if err != nil {
panic(fmt.Sprintf("invalid file URI: %s", uri))
}
if parsed.Host != "" {
return "//" + parsed.Host + parsed.Path
}
return fixWindowsURIPath(parsed.Path)
}
// Leave all other URIs escaped so we can round-trip them.
scheme, path, ok := strings.Cut(string(uri), ":")
if !ok {
panic(fmt.Sprintf("invalid URI: %s", uri))
}
authority := "ts-nul-authority"
if rest, ok := strings.CutPrefix(path, "//"); ok {
authority, path, ok = strings.Cut(rest, "/")
if !ok {
panic(fmt.Sprintf("invalid URI: %s", uri))
}
}
return "^/" + scheme + "/" + authority + "/" + path
}
func (uri DocumentUri) Path(useCaseSensitiveFileNames bool) tspath.Path {
fileName := uri.FileName()
return tspath.ToPath(fileName, "", useCaseSensitiveFileNames)
}
func fixWindowsURIPath(path string) string {
if rest, ok := strings.CutPrefix(path, "/"); ok {
if volume, rest, ok := tspath.SplitVolumePath(rest); ok {
return volume + rest
}
}
return path
}
type HasTextDocumentURI interface {
TextDocumentURI() DocumentUri
}
type HasTextDocumentPosition interface {
HasTextDocumentURI
TextDocumentPosition() Position
}
type HasLocations interface {
GetLocations() *[]Location
}
type HasLocation interface {
GetLocation() Location
}
type URI string // !!!
type Method string
func errNotObject(k json.Kind) error {
return fmt.Errorf("expected object start, but encountered %v", k)
}
func errNull(field string) error {
return fmt.Errorf("null value is not allowed for field %q", field)
}
func errMissing(props []string) error {
return fmt.Errorf("missing required properties: %s", strings.Join(props, ", "))
}
func errInvalidKind(typeName string, got json.Kind) error {
return fmt.Errorf("invalid %s: got %v", typeName, got)
}
func errInvalidValue(typeName string, data []byte) error {
return fmt.Errorf("invalid %s: %s", typeName, data)
}
func errLiteralMismatch(typeName string, expected string, got []byte) error {
return fmt.Errorf("expected %s value %s, got %s", typeName, expected, got)
}
func assertOnlyOne(message string, count int) {
if count != 1 {
panic(message)
}
}
func assertAtMostOne(message string, count int) {
if count > 1 {
panic(message)
}
}
// jsonKeyCheck compares a raw JSON key token (including quotes) against a Go string.
func jsonKeyCheck(name []byte, key string) bool {
return len(name) == len(key)+2 && name[0] == '"' && string(name[1:len(name)-1]) == key
}
// jsonObjectRawField scans the top-level keys of a JSON object looking for the
// given field name, and returns its raw JSON value (e.g. `"full"` with quotes).
// Returns nil if the field is not found.
func jsonObjectRawField(data []byte, field string) json.Value {
dec := json.NewDecoder(bytes.NewBuffer(data))
if dec.PeekKind() != '{' {
return nil
}
if _, err := dec.ReadToken(); err != nil {
return nil
}
for dec.PeekKind() != '}' {
name, err := dec.ReadValue()
if err != nil {
return nil
}
if jsonKeyCheck(name, field) {
val, err := dec.ReadValue()
if err != nil {
return nil
}
return val
}
if err := dec.SkipValue(); err != nil {
return nil
}
}
return nil
}
// jsonObjectHasKey scans the top-level keys of a JSON object looking for any of the
// given keys. Returns the index of the first key found, or -1 if none match.
// Bails early on first match without decoding any values.
func jsonObjectHasKey(data []byte, keys ...string) int {
dec := json.NewDecoder(bytes.NewBuffer(data))
if dec.PeekKind() != '{' {
return -1
}
if _, err := dec.ReadToken(); err != nil {
return -1
}
for dec.PeekKind() != '}' {
name, err := dec.ReadValue()
if err != nil {
return -1
}
for i, key := range keys {
if jsonKeyCheck(name, key) {
return i
}
}
if err := dec.SkipValue(); err != nil {
return -1
}
}
return -1
}
// Inspired by https://www.youtube.com/watch?v=dab3I-HcTVk
type RequestInfo[Params, Resp any] struct {
_ [0]Params
_ [0]Resp
Method Method
}
func (info RequestInfo[Params, Resp]) UnmarshalResult(result any) (Resp, error) {
raw, ok := result.(json.Value)
if !ok {
return *new(Resp), fmt.Errorf("expected json.Value, got %T", result)
}
var r Resp
if err := json.Unmarshal(raw, &r); err != nil {
return *new(Resp), err
}
return r, nil
}
func (info RequestInfo[Params, Resp]) NewRequestMessage(id *jsonrpc.ID, params Params) *RequestMessage {
return &RequestMessage{
ID: id,
Method: info.Method,
Params: params,
}
}
type NotificationInfo[Params any] struct {
_ [0]Params
Method Method
}
func (info NotificationInfo[Params]) NewNotificationMessage(params Params) *RequestMessage {
return &RequestMessage{
Method: info.Method,
Params: params,
}
}
// UnmarshalParams decodes the params of an inbound request or notification
// message into the requested type. Inbound messages store their params as a
// raw [json.Value] (see [Message.UnmarshalJSON]); decoding is deferred to the
// point of dispatch so that param types for methods the server never handles
// are not forced into the binary.
//
// A [NoParams] method must be given no params; every other method must be given
// params as an object or array. A violation returns [ErrorCodeInvalidParams].
func UnmarshalParams[T any](req *RequestMessage) (T, error) {
var params T
var raw json.Value
if req.Params != nil {
v, ok := req.Params.(json.Value)
if !ok {
return params, fmt.Errorf("%w: unexpected params type %T", ErrorCodeInvalidParams, req.Params)
}
raw = v
}
// params is the zero value of T; this asserts on its type, i.e. whether the
// method was declared with NoParams.
if _, declaresNoParams := any(params).(NoParams); declaresNoParams {
if len(raw) != 0 {
return params, fmt.Errorf("%w: expected no params, got %s", ErrorCodeInvalidParams, raw)
}
return params, nil
}
// The base protocol defines params as `array | object`; reject anything else
// (absent, null, or a scalar).
if k := raw.Kind(); k != '{' && k != '[' {
return params, fmt.Errorf("%w: params must be an object or array", ErrorCodeInvalidParams)
}
if err := json.Unmarshal(raw, &params); err != nil {
return params, fmt.Errorf("%w: %w", ErrorCodeInvalidParams, err)
}
return params, nil
}
type Null struct{}
func (Null) UnmarshalJSONFrom(dec *json.Decoder) error {
data, err := dec.ReadValue()
if err != nil {
return err
}
if string(data) != "null" {
return fmt.Errorf("expected null, got %s", data)
}
return nil
}
func (Null) MarshalJSONTo(enc *json.Encoder) error {
return enc.WriteToken(json.Null)
}
type NoParams struct{}
func (NoParams) IsZero() bool { return true }
type clientCapabilitiesKey struct{}
func WithClientCapabilities(ctx context.Context, caps *ResolvedClientCapabilities) context.Context {
return context.WithValue(ctx, clientCapabilitiesKey{}, caps)
}
func GetClientCapabilities(ctx context.Context) *ResolvedClientCapabilities {
if caps, _ := ctx.Value(clientCapabilitiesKey{}).(*ResolvedClientCapabilities); caps != nil {
return caps
}
return &ResolvedClientCapabilities{}
}
// PreferredMarkupKind returns the first (most preferred) markup kind from the given formats,
// or MarkupKindPlainText if the slice is empty.
func PreferredMarkupKind(formats []MarkupKind) MarkupKind {
if len(formats) > 0 {
return formats[0]
}
return MarkupKindPlainText
}
const (
CodeActionKindSourceRemoveUnusedImports CodeActionKind = "source.removeUnusedImports"
CodeActionKindSourceSortImports CodeActionKind = "source.sortImports"
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
package lsproto
import (
"testing"
"github.com/microsoft/typescript-go/internal/json"
"gotest.tools/v3/assert"
)
func TestUnmarshalCompletionItem(t *testing.T) {
t.Parallel()
const message = `{
"label": "pageXOffset",
"insertTextFormat": 1,
"textEdit": {
"newText": "pageXOffset",
"insert": {
"start": {
"line": 4,
"character": 0
},
"end": {
"line": 4,
"character": 4
}
},
"replace": {
"start": {
"line": 4,
"character": 0
},
"end": {
"line": 4,
"character": 4
}
}
},
"kind": 6,
"sortText": "15",
"commitCharacters": [
".",
",",
";"
]
}`
var result CompletionItem
err := json.Unmarshal([]byte(message), &result)
assert.NilError(t, err)
assert.DeepEqual(t, result, CompletionItem{
Label: "pageXOffset",
InsertTextFormat: new(InsertTextFormatPlainText),
TextEdit: &TextEditOrInsertReplaceEdit{
InsertReplaceEdit: &InsertReplaceEdit{
NewText: "pageXOffset",
Insert: Range{
Start: Position{
Line: 4,
Character: 0,
},
End: Position{
Line: 4,
Character: 4,
},
},
Replace: Range{
Start: Position{
Line: 4,
Character: 0,
},
End: Position{
Line: 4,
Character: 4,
},
},
},
},
Kind: new(CompletionItemKindVariable),
SortText: new("15"),
CommitCharacters: new([]string{".", ",", ";"}),
})
}

View File

@@ -0,0 +1,164 @@
package lsproto
import (
"reflect"
"strings"
"sync"
"github.com/microsoft/typescript-go/internal/json"
)
// This file provides a single reflection-driven object decoder that replaces
// the per-type UnmarshalJSONFrom methods the generator emits for plain
// structs. It preserves the same strictness those methods enforce: the value
// must be an object, all required fields must be present, and a JSON null is
// rejected for nilable fields whose spec is not nullable. Required fields, and
// the rare spec-nullable fields, are marked with an `lsp:"required"` /
// `lsp:"nullable"` struct tag (the spec nullability that decides this is not
// otherwise recoverable from the json tag); any other nilable field rejects
// null by default. The per-type spec is resolved once via reflection and
// cached, so the only per-call work is the object scan.
type structFieldSpec struct {
index int
requiredID int // bit position among required fields, or -1
rejectNull bool
}
type structSpec struct {
byName map[string]structFieldSpec
requiredNames []string
requiredMask uint64
}
var structSpecCache sync.Map // reflect.Type -> *structSpec
func specFor(t reflect.Type) *structSpec {
if cached, ok := structSpecCache.Load(t); ok {
return cached.(*structSpec)
}
spec := &structSpec{byName: make(map[string]structFieldSpec, t.NumField())}
for i := range t.NumField() {
f := t.Field(i)
jsonName, _, _ := strings.Cut(f.Tag.Get("json"), ",")
if jsonName == "" || jsonName == "-" {
continue
}
fs := structFieldSpec{index: i, requiredID: -1}
var nullable bool
for marker := range strings.SplitSeq(f.Tag.Get("lsp"), ",") {
switch marker {
case "required":
fs.requiredID = len(spec.requiredNames)
spec.requiredMask |= 1 << fs.requiredID
spec.requiredNames = append(spec.requiredNames, jsonName)
case "nullable":
nullable = true
}
}
// A nilable field (pointer/slice/map) rejects an explicit JSON null
// unless the spec marks it nullable.
switch f.Type.Kind() {
case reflect.Pointer, reflect.Slice, reflect.Map:
fs.rejectNull = !nullable
}
spec.byName[jsonName] = fs
}
actual, _ := structSpecCache.LoadOrStore(t, spec)
return actual.(*structSpec)
}
// unmarshalStruct decodes a JSON object into the struct pointed to by v,
// enforcing object-kind, required-field, and non-nullable-field strictness as
// declared by lsp struct tags. Up to 64 required fields are supported.
func unmarshalStruct(v any, dec *json.Decoder) error {
rv := reflect.ValueOf(v).Elem()
spec := specFor(rv.Type())
if k := dec.PeekKind(); k != '{' {
return errNotObject(k)
}
if _, err := dec.ReadToken(); err != nil {
return err
}
var seen uint64
for dec.PeekKind() != '}' {
name, err := dec.ReadValue()
if err != nil {
return err
}
// name includes surrounding quotes; m[string(b)] is a no-alloc lookup.
fs, ok := spec.byName[string(name[1:len(name)-1])]
if !ok {
if err := dec.SkipValue(); err != nil {
return err
}
continue
}
if fs.requiredID >= 0 {
seen |= 1 << fs.requiredID
}
if fs.rejectNull && dec.PeekKind() == 'n' {
return errNull(string(name[1 : len(name)-1]))
}
if err := json.UnmarshalDecode(dec, rv.Field(fs.index).Addr().Interface()); err != nil {
return err
}
}
if _, err := dec.ReadToken(); err != nil {
return err
}
if missing := spec.requiredMask &^ seen; missing != 0 {
var missingProps []string
for id, n := range spec.requiredNames {
if missing&(1<<id) != 0 {
missingProps = append(missingProps, n)
}
}
return errMissing(missingProps)
}
return nil
}
// marshalUnion encodes a union struct whose fields are all pointers, exactly
// one of which is set. It writes the single non-nil field; if nullable, an
// empty union marshals as null, otherwise an empty union is a programming
// error. The name is only used for the panic message.
func marshalUnion(v any, enc *json.Encoder, name string, nullable bool) error {
rv := reflect.ValueOf(v).Elem()
var set reflect.Value
count := 0
for _, f := range rv.Fields() {
if !f.IsNil() {
count++
if !set.IsValid() {
set = f
}
}
}
if nullable {
assertAtMostOne("more than one element of "+name+" is set", count)
if !set.IsValid() {
return enc.WriteToken(json.Null)
}
} else {
assertOnlyOne("exactly one element of "+name+" should be set", count)
}
return json.MarshalEncode(enc, set.Interface())
}
// countNonNil returns the number of non-nil pointer/slice/map fields in the
// struct pointed to by v. Used to assert externally-tagged unions have exactly
// one arm set.
func countNonNil(v any) int {
rv := reflect.ValueOf(v).Elem()
count := 0
for _, f := range rv.Fields() {
if !f.IsNil() {
count++
}
}
return count
}

View File

@@ -0,0 +1,37 @@
package lsproto
import (
"cmp"
)
// Implements a cmp.Compare like function for two Position
// ComparePositions(pos, other) == cmp.Compare(pos, other)
func ComparePositions(pos, other Position) int {
if lineComp := cmp.Compare(pos.Line, other.Line); lineComp != 0 {
return lineComp
}
return cmp.Compare(pos.Character, other.Character)
}
// Implements a cmp.Compare like function for two Range
// CompareRanges(lsRange, other) == cmp.Compare(lsRange, other)
//
// Range.Start is compared before Range.End
func CompareRanges(lsRange, other Range) int {
if startComp := ComparePositions(lsRange.Start, other.Start); startComp != 0 {
return startComp
}
return ComparePositions(lsRange.End, other.End)
}
// AsString returns the plain text of a StringOrMarkupContent, reading the
// MarkupContent value when the message is not a plain string.
func (m StringOrMarkupContent) AsString() string {
if m.String != nil {
return *m.String
}
if m.MarkupContent != nil {
return m.MarkupContent.Value
}
return ""
}