add prune and remove unused packages

This commit is contained in:
Michelle Au
2019-03-08 14:54:43 -08:00
parent f59b58d164
commit 8c0accad66
17240 changed files with 27 additions and 4750030 deletions

View File

@@ -1,239 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fastwalk_test
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strings"
"sync"
"testing"
"golang.org/x/tools/internal/fastwalk"
)
func formatFileModes(m map[string]os.FileMode) string {
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, k := range keys {
fmt.Fprintf(&buf, "%-20s: %v\n", k, m[k])
}
return buf.String()
}
func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) {
tempdir, err := ioutil.TempDir("", "test-fast-walk")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempdir)
for path, contents := range files {
file := filepath.Join(tempdir, "/src", path)
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
t.Fatal(err)
}
var err error
if strings.HasPrefix(contents, "LINK:") {
err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file)
} else {
err = ioutil.WriteFile(file, []byte(contents), 0644)
}
if err != nil {
t.Fatal(err)
}
}
got := map[string]os.FileMode{}
var mu sync.Mutex
if err := fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error {
mu.Lock()
defer mu.Unlock()
if !strings.HasPrefix(path, tempdir) {
t.Fatalf("bogus prefix on %q, expect %q", path, tempdir)
}
key := filepath.ToSlash(strings.TrimPrefix(path, tempdir))
if old, dup := got[key]; dup {
t.Fatalf("callback called twice for key %q: %v -> %v", key, old, typ)
}
got[key] = typ
return callback(path, typ)
}); err != nil {
t.Fatalf("callback returned: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want))
}
}
func TestFastWalk_Basic(t *testing.T) {
testFastWalk(t, map[string]string{
"foo/foo.go": "one",
"bar/bar.go": "two",
"skip/skip.go": "skip",
},
func(path string, typ os.FileMode) error {
return nil
},
map[string]os.FileMode{
"": os.ModeDir,
"/src": os.ModeDir,
"/src/bar": os.ModeDir,
"/src/bar/bar.go": 0,
"/src/foo": os.ModeDir,
"/src/foo/foo.go": 0,
"/src/skip": os.ModeDir,
"/src/skip/skip.go": 0,
})
}
func TestFastWalk_LongFileName(t *testing.T) {
longFileName := strings.Repeat("x", 255)
testFastWalk(t, map[string]string{
longFileName: "one",
},
func(path string, typ os.FileMode) error {
return nil
},
map[string]os.FileMode{
"": os.ModeDir,
"/src": os.ModeDir,
"/src/" + longFileName: 0,
},
)
}
func TestFastWalk_Symlink(t *testing.T) {
switch runtime.GOOS {
case "windows", "plan9":
t.Skipf("skipping on %s", runtime.GOOS)
}
testFastWalk(t, map[string]string{
"foo/foo.go": "one",
"bar/bar.go": "LINK:../foo.go",
"symdir": "LINK:foo",
},
func(path string, typ os.FileMode) error {
return nil
},
map[string]os.FileMode{
"": os.ModeDir,
"/src": os.ModeDir,
"/src/bar": os.ModeDir,
"/src/bar/bar.go": os.ModeSymlink,
"/src/foo": os.ModeDir,
"/src/foo/foo.go": 0,
"/src/symdir": os.ModeSymlink,
})
}
func TestFastWalk_SkipDir(t *testing.T) {
testFastWalk(t, map[string]string{
"foo/foo.go": "one",
"bar/bar.go": "two",
"skip/skip.go": "skip",
},
func(path string, typ os.FileMode) error {
if typ == os.ModeDir && strings.HasSuffix(path, "skip") {
return filepath.SkipDir
}
return nil
},
map[string]os.FileMode{
"": os.ModeDir,
"/src": os.ModeDir,
"/src/bar": os.ModeDir,
"/src/bar/bar.go": 0,
"/src/foo": os.ModeDir,
"/src/foo/foo.go": 0,
"/src/skip": os.ModeDir,
})
}
func TestFastWalk_SkipFiles(t *testing.T) {
// Directory iteration order is undefined, so there's no way to know
// which file to expect until the walk happens. Rather than mess
// with the test infrastructure, just mutate want.
var mu sync.Mutex
want := map[string]os.FileMode{
"": os.ModeDir,
"/src": os.ModeDir,
"/src/zzz": os.ModeDir,
"/src/zzz/c.go": 0,
}
testFastWalk(t, map[string]string{
"a_skipfiles.go": "a",
"b_skipfiles.go": "b",
"zzz/c.go": "c",
},
func(path string, typ os.FileMode) error {
if strings.HasSuffix(path, "_skipfiles.go") {
mu.Lock()
defer mu.Unlock()
want["/src/"+filepath.Base(path)] = 0
return fastwalk.SkipFiles
}
return nil
},
want)
if len(want) != 5 {
t.Errorf("saw too many files: wanted 5, got %v (%v)", len(want), want)
}
}
func TestFastWalk_TraverseSymlink(t *testing.T) {
switch runtime.GOOS {
case "windows", "plan9":
t.Skipf("skipping on %s", runtime.GOOS)
}
testFastWalk(t, map[string]string{
"foo/foo.go": "one",
"bar/bar.go": "two",
"skip/skip.go": "skip",
"symdir": "LINK:foo",
},
func(path string, typ os.FileMode) error {
if typ == os.ModeSymlink {
return fastwalk.TraverseLink
}
return nil
},
map[string]os.FileMode{
"": os.ModeDir,
"/src": os.ModeDir,
"/src/bar": os.ModeDir,
"/src/bar/bar.go": 0,
"/src/foo": os.ModeDir,
"/src/foo/foo.go": 0,
"/src/skip": os.ModeDir,
"/src/skip/skip.go": 0,
"/src/symdir": os.ModeSymlink,
"/src/symdir/foo.go": 0,
})
}
var benchDir = flag.String("benchdir", runtime.GOROOT(), "The directory to scan for BenchmarkFastWalk")
func BenchmarkFastWalk(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
err := fastwalk.Walk(*benchDir, func(path string, typ os.FileMode) error { return nil })
if err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,135 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gopathwalk
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
)
func TestShouldTraverse(t *testing.T) {
switch runtime.GOOS {
case "windows", "plan9":
t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS)
}
dir, err := ioutil.TempDir("", "goimports-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Note: mapToDir prepends "src" to each element, since
// mapToDir was made for creating GOPATHs.
if err := mapToDir(dir, map[string]string{
"foo/foo2/file.txt": "",
"foo/foo2/link-to-src": "LINK:../../",
"foo/foo2/link-to-src-foo": "LINK:../../foo",
"foo/foo2/link-to-dot": "LINK:.",
"bar/bar2/file.txt": "",
"bar/bar2/link-to-src-foo": "LINK:../../foo",
"a/b/c": "LINK:../../a/d",
"a/d/e": "LINK:../../a/b",
}); err != nil {
t.Fatal(err)
}
tests := []struct {
dir string
file string
want bool
}{
{
dir: "src/foo/foo2",
file: "link-to-src-foo",
want: false, // loop
},
{
dir: "src/foo/foo2",
file: "link-to-src",
want: false, // loop
},
{
dir: "src/foo/foo2",
file: "link-to-dot",
want: false, // loop
},
{
dir: "src/bar/bar2",
file: "link-to-src-foo",
want: true, // not a loop
},
{
dir: "src/a/b/c",
file: "e",
want: false, // loop: "e" is the same as "b".
},
}
for i, tt := range tests {
fi, err := os.Stat(filepath.Join(dir, tt.dir, tt.file))
if err != nil {
t.Errorf("%d. Stat = %v", i, err)
continue
}
var w walker
got := w.shouldTraverse(filepath.Join(dir, tt.dir), fi)
if got != tt.want {
t.Errorf("%d. shouldTraverse(%q, %q) = %v; want %v", i, tt.dir, tt.file, got, tt.want)
}
}
}
// TestSkip tests that various goimports rules are followed in non-modules mode.
func TestSkip(t *testing.T) {
dir, err := ioutil.TempDir("", "goimports-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
if err := mapToDir(dir, map[string]string{
"ignoreme/f.go": "package ignoreme", // ignored by .goimportsignore
"node_modules/f.go": "package nodemodules;", // ignored by hardcoded node_modules filter
"v/f.go": "package v;", // ignored by hardcoded vgo cache rule
"mod/f.go": "package mod;", // ignored by hardcoded vgo cache rule
"shouldfind/f.go": "package shouldfind;", // not ignored
".goimportsignore": "ignoreme\n",
}); err != nil {
t.Fatal(err)
}
var found []string
walkDir(Root{filepath.Join(dir, "src"), RootGOPATH}, func(root Root, dir string) {
found = append(found, dir[len(root.Path)+1:])
}, Options{ModulesEnabled: false, Debug: true})
if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) {
t.Errorf("expected to find only %v, got %v", want, found)
}
}
func mapToDir(destDir string, files map[string]string) error {
for path, contents := range files {
file := filepath.Join(destDir, "src", path)
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
return err
}
var err error
if strings.HasPrefix(contents, "LINK:") {
err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file)
} else {
err = ioutil.WriteFile(file, []byte(contents), 0644)
}
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,358 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package jsonrpc2 is a minimal implementation of the JSON RPC 2 spec.
// https://www.jsonrpc.org/specification
// It is intended to be compatible with other implementations at the wire level.
package jsonrpc2
import (
"context"
"encoding/json"
"fmt"
"sync"
"sync/atomic"
"time"
)
// Conn is a JSON RPC 2 client server connection.
// Conn is bidirectional; it does not have a designated server or client end.
type Conn struct {
handle Handler
cancel Canceler
log Logger
stream Stream
done chan struct{}
err error
seq int64 // must only be accessed using atomic operations
pendingMu sync.Mutex // protects the pending map
pending map[ID]chan *Response
handlingMu sync.Mutex // protects the handling map
handling map[ID]handling
}
// Handler is an option you can pass to NewConn to handle incoming requests.
// If the request returns false from IsNotify then the Handler must eventually
// call Reply on the Conn with the supplied request.
// Handlers are called synchronously, they should pass the work off to a go
// routine if they are going to take a long time.
type Handler func(context.Context, *Conn, *Request)
// Canceler is an option you can pass to NewConn which is invoked for
// cancelled outgoing requests.
// The request will have the ID filled in, which can be used to propagate the
// cancel to the other process if needed.
// It is okay to use the connection to send notifications, but the context will
// be in the cancelled state, so you must do it with the background context
// instead.
type Canceler func(context.Context, *Conn, *Request)
// NewErrorf builds a Error struct for the suppied message and code.
// If args is not empty, message and args will be passed to Sprintf.
func NewErrorf(code int64, format string, args ...interface{}) *Error {
return &Error{
Code: code,
Message: fmt.Sprintf(format, args...),
}
}
// NewConn creates a new connection object that reads and writes messages from
// the supplied stream and dispatches incoming messages to the supplied handler.
func NewConn(ctx context.Context, s Stream, options ...interface{}) *Conn {
conn := &Conn{
stream: s,
done: make(chan struct{}),
pending: make(map[ID]chan *Response),
handling: make(map[ID]handling),
}
for _, opt := range options {
switch opt := opt.(type) {
case Handler:
if conn.handle != nil {
panic("Duplicate Handler function in options list")
}
conn.handle = opt
case Canceler:
if conn.cancel != nil {
panic("Duplicate Canceler function in options list")
}
conn.cancel = opt
case Logger:
if conn.log != nil {
panic("Duplicate Logger function in options list")
}
conn.log = opt
default:
panic(fmt.Errorf("Unknown option type %T in options list", opt))
}
}
if conn.handle == nil {
// the default handler reports a method error
conn.handle = func(ctx context.Context, c *Conn, r *Request) {
if r.IsNotify() {
c.Reply(ctx, r, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
}
}
}
if conn.cancel == nil {
// the default canceller does nothing
conn.cancel = func(context.Context, *Conn, *Request) {}
}
if conn.log == nil {
// the default logger does nothing
conn.log = func(Direction, *ID, time.Duration, string, *json.RawMessage, *Error) {}
}
go func() {
conn.err = conn.run(ctx)
close(conn.done)
}()
return conn
}
// Wait blocks until the connection is terminated, and returns any error that
// cause the termination.
func (c *Conn) Wait(ctx context.Context) error {
select {
case <-c.done:
return c.err
case <-ctx.Done():
return ctx.Err()
}
}
// Cancel cancels a pending Call on the server side.
// The call is identified by its id.
// JSON RPC 2 does not specify a cancel message, so cancellation support is not
// directly wired in. This method allows a higher level protocol to choose how
// to propagate the cancel.
func (c *Conn) Cancel(id ID) {
c.handlingMu.Lock()
handling, found := c.handling[id]
c.handlingMu.Unlock()
if found {
handling.cancel()
}
}
// Notify is called to send a notification request over the connection.
// It will return as soon as the notification has been sent, as no response is
// possible.
func (c *Conn) Notify(ctx context.Context, method string, params interface{}) error {
jsonParams, err := marshalToRaw(params)
if err != nil {
return fmt.Errorf("marshalling notify parameters: %v", err)
}
request := &Request{
Method: method,
Params: jsonParams,
}
data, err := json.Marshal(request)
if err != nil {
return fmt.Errorf("marshalling notify request: %v", err)
}
c.log(Send, nil, -1, request.Method, request.Params, nil)
return c.stream.Write(ctx, data)
}
// Call sends a request over the connection and then waits for a response.
// If the response is not an error, it will be decoded into result.
// result must be of a type you an pass to json.Unmarshal.
func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) error {
jsonParams, err := marshalToRaw(params)
if err != nil {
return fmt.Errorf("marshalling call parameters: %v", err)
}
// generate a new request identifier
id := ID{Number: atomic.AddInt64(&c.seq, 1)}
request := &Request{
ID: &id,
Method: method,
Params: jsonParams,
}
// marshal the request now it is complete
data, err := json.Marshal(request)
if err != nil {
return fmt.Errorf("marshalling call request: %v", err)
}
// we have to add ourselves to the pending map before we send, otherwise we
// are racing the response
rchan := make(chan *Response)
c.pendingMu.Lock()
c.pending[id] = rchan
c.pendingMu.Unlock()
defer func() {
// clean up the pending response handler on the way out
c.pendingMu.Lock()
delete(c.pending, id)
c.pendingMu.Unlock()
}()
// now we are ready to send
before := time.Now()
c.log(Send, request.ID, -1, request.Method, request.Params, nil)
if err := c.stream.Write(ctx, data); err != nil {
// sending failed, we will never get a response, so don't leave it pending
return err
}
// now wait for the response
select {
case response := <-rchan:
elapsed := time.Since(before)
c.log(Send, response.ID, elapsed, request.Method, response.Result, response.Error)
// is it an error response?
if response.Error != nil {
return response.Error
}
if result == nil || response.Result == nil {
return nil
}
if err := json.Unmarshal(*response.Result, result); err != nil {
return fmt.Errorf("unmarshalling result: %v", err)
}
return nil
case <-ctx.Done():
// allow the handler to propagate the cancel
c.cancel(ctx, c, request)
return ctx.Err()
}
}
// Reply sends a reply to the given request.
// It is an error to call this if request was not a call.
// You must call this exactly once for any given request.
// If err is set then result will be ignored.
func (c *Conn) Reply(ctx context.Context, req *Request, result interface{}, err error) error {
if req.IsNotify() {
return fmt.Errorf("reply not invoked with a valid call")
}
c.handlingMu.Lock()
handling, found := c.handling[*req.ID]
if found {
delete(c.handling, *req.ID)
}
c.handlingMu.Unlock()
if !found {
return fmt.Errorf("not a call in progress: %v", req.ID)
}
elapsed := time.Since(handling.start)
var raw *json.RawMessage
if err == nil {
raw, err = marshalToRaw(result)
}
response := &Response{
Result: raw,
ID: req.ID,
}
if err != nil {
if callErr, ok := err.(*Error); ok {
response.Error = callErr
} else {
response.Error = NewErrorf(0, "%s", err)
}
}
data, err := json.Marshal(response)
if err != nil {
return err
}
c.log(Send, response.ID, elapsed, req.Method, response.Result, response.Error)
if err = c.stream.Write(ctx, data); err != nil {
// TODO(iancottrell): if a stream write fails, we really need to shut down
// the whole stream
return err
}
return nil
}
type handling struct {
request *Request
cancel context.CancelFunc
start time.Time
}
// combined has all the fields of both Request and Response.
// We can decode this and then work out which it is.
type combined struct {
VersionTag VersionTag `json:"jsonrpc"`
ID *ID `json:"id,omitempty"`
Method string `json:"method"`
Params *json.RawMessage `json:"params,omitempty"`
Result *json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
// Run starts a read loop on the supplied reader.
// It must be called exactly once for each Conn.
// It returns only when the reader is closed or there is an error in the stream.
func (c *Conn) run(ctx context.Context) error {
for {
// get the data for a message
data, err := c.stream.Read(ctx)
if err != nil {
// the stream failed, we cannot continue
return err
}
// read a combined message
msg := &combined{}
if err := json.Unmarshal(data, msg); err != nil {
// a badly formed message arrived, log it and continue
// we trust the stream to have isolated the error to just this message
c.log(Receive, nil, -1, "", nil, NewErrorf(0, "unmarshal failed: %v", err))
continue
}
// work out which kind of message we have
switch {
case msg.Method != "":
// if method is set it must be a request
request := &Request{
Method: msg.Method,
Params: msg.Params,
ID: msg.ID,
}
if request.IsNotify() {
c.log(Receive, request.ID, -1, request.Method, request.Params, nil)
// we have a Notify, forward to the handler in a go routine
c.handle(ctx, c, request)
} else {
// we have a Call, forward to the handler in another go routine
reqCtx, cancelReq := context.WithCancel(ctx)
c.handlingMu.Lock()
c.handling[*request.ID] = handling{
request: request,
cancel: cancelReq,
start: time.Now(),
}
c.handlingMu.Unlock()
c.log(Receive, request.ID, -1, request.Method, request.Params, nil)
c.handle(reqCtx, c, request)
}
case msg.ID != nil:
// we have a response, get the pending entry from the map
c.pendingMu.Lock()
rchan := c.pending[*msg.ID]
if rchan != nil {
delete(c.pending, *msg.ID)
}
c.pendingMu.Unlock()
// and send the reply to the channel
response := &Response{
Result: msg.Result,
Error: msg.Error,
ID: msg.ID,
}
rchan <- response
close(rchan)
default:
c.log(Receive, nil, -1, "", nil, NewErrorf(0, "message not a call, notify or response, ignoring"))
}
}
}
func marshalToRaw(obj interface{}) (*json.RawMessage, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
raw := json.RawMessage(data)
return &raw, nil
}

View File

@@ -1,163 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonrpc2_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"path"
"reflect"
"testing"
"golang.org/x/tools/internal/jsonrpc2"
)
var logRPC = flag.Bool("logrpc", false, "Enable jsonrpc2 communication logging")
type callTest struct {
method string
params interface{}
expect interface{}
}
var callTests = []callTest{
{"no_args", nil, true},
{"one_string", "fish", "got:fish"},
{"one_number", 10, "got:10"},
{"join", []string{"a", "b", "c"}, "a/b/c"},
//TODO: expand the test cases
}
func (test *callTest) newResults() interface{} {
switch e := test.expect.(type) {
case []interface{}:
var r []interface{}
for _, v := range e {
r = append(r, reflect.New(reflect.TypeOf(v)).Interface())
}
return r
case nil:
return nil
default:
return reflect.New(reflect.TypeOf(test.expect)).Interface()
}
}
func (test *callTest) verifyResults(t *testing.T, results interface{}) {
if results == nil {
return
}
val := reflect.Indirect(reflect.ValueOf(results)).Interface()
if !reflect.DeepEqual(val, test.expect) {
t.Errorf("%v:Results are incorrect, got %+v expect %+v", test.method, val, test.expect)
}
}
func TestPlainCall(t *testing.T) {
ctx := context.Background()
a, b := prepare(ctx, t, false)
for _, test := range callTests {
results := test.newResults()
if err := a.Call(ctx, test.method, test.params, results); err != nil {
t.Fatalf("%v:Call failed: %v", test.method, err)
}
test.verifyResults(t, results)
if err := b.Call(ctx, test.method, test.params, results); err != nil {
t.Fatalf("%v:Call failed: %v", test.method, err)
}
test.verifyResults(t, results)
}
}
func TestHeaderCall(t *testing.T) {
ctx := context.Background()
a, b := prepare(ctx, t, true)
for _, test := range callTests {
results := test.newResults()
if err := a.Call(ctx, test.method, test.params, results); err != nil {
t.Fatalf("%v:Call failed: %v", test.method, err)
}
test.verifyResults(t, results)
if err := b.Call(ctx, test.method, test.params, results); err != nil {
t.Fatalf("%v:Call failed: %v", test.method, err)
}
test.verifyResults(t, results)
}
}
func prepare(ctx context.Context, t *testing.T, withHeaders bool) (*testHandler, *testHandler) {
a := &testHandler{t: t}
b := &testHandler{t: t}
a.reader, b.writer = io.Pipe()
b.reader, a.writer = io.Pipe()
for _, h := range []*testHandler{a, b} {
h := h
if withHeaders {
h.stream = jsonrpc2.NewHeaderStream(h.reader, h.writer)
} else {
h.stream = jsonrpc2.NewStream(h.reader, h.writer)
}
args := []interface{}{jsonrpc2.Handler(handle)}
if *logRPC {
args = append(args, jsonrpc2.Log)
}
h.Conn = jsonrpc2.NewConn(ctx, h.stream, args...)
go func() {
defer func() {
h.reader.Close()
h.writer.Close()
}()
if err := h.Conn.Wait(ctx); err != nil {
t.Fatalf("Stream failed: %v", err)
}
}()
}
return a, b
}
type testHandler struct {
t *testing.T
reader *io.PipeReader
writer *io.PipeWriter
stream jsonrpc2.Stream
*jsonrpc2.Conn
}
func handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) {
switch r.Method {
case "no_args":
if r.Params != nil {
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return
}
c.Reply(ctx, r, true, nil)
case "one_string":
var v string
if err := json.Unmarshal(*r.Params, &v); err != nil {
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
return
}
c.Reply(ctx, r, "got:"+v, nil)
case "one_number":
var v int
if err := json.Unmarshal(*r.Params, &v); err != nil {
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
return
}
c.Reply(ctx, r, fmt.Sprintf("got:%d", v), nil)
case "join":
var v []string
if err := json.Unmarshal(*r.Params, &v); err != nil {
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
return
}
c.Reply(ctx, r, path.Join(v...), nil)
default:
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
}
}

View File

@@ -1,59 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonrpc2
import (
"encoding/json"
"log"
"time"
)
// Logger is an option you can pass to NewConn which is invoked for
// all messages flowing through a Conn.
// direction indicates if the message being recieved or sent
// id is the message id, if not set it was a notification
// elapsed is the time between a call being seen and the response, and is
// negative for anything that is not a response.
// method is the method name specified in the message
// payload is the parameters for a call or notification, and the result for a
// response
type Logger = func(direction Direction, id *ID, elapsed time.Duration, method string, payload *json.RawMessage, err *Error)
// Direction is used to indicate to a logger whether the logged message was being
// sent or received.
type Direction bool
const (
// Send indicates the message is outgoing.
Send = Direction(true)
// Receive indicates the message is incoming.
Receive = Direction(false)
)
func (d Direction) String() string {
switch d {
case Send:
return "send"
case Receive:
return "receive"
default:
panic("unreachable")
}
}
// Log is an implementation of Logger that outputs using log.Print
// It is not used by default, but is provided for easy logging in users code.
func Log(direction Direction, id *ID, elapsed time.Duration, method string, payload *json.RawMessage, err *Error) {
switch {
case err != nil:
log.Printf("%v failure [%v] %s %v", direction, id, method, err)
case id == nil:
log.Printf("%v notification %s %s", direction, method, *payload)
case elapsed >= 0:
log.Printf("%v response in %v [%v] %s %s", direction, elapsed, id, method, *payload)
default:
log.Printf("%v call [%v] %s %s", direction, id, method, *payload)
}
}

View File

@@ -1,146 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonrpc2
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"sync"
)
// Stream abstracts the transport mechanics from the JSON RPC protocol.
// A Conn reads and writes messages using the stream it was provided on
// construction, and assumes that each call to Read or Write fully transfers
// a single message, or returns an error.
type Stream interface {
// Read gets the next message from the stream.
// It is never called concurrently.
Read(context.Context) ([]byte, error)
// Write sends a message to the stream.
// It must be safe for concurrent use.
Write(context.Context, []byte) error
}
// NewStream returns a Stream built on top of an io.Reader and io.Writer
// The messages are sent with no wrapping, and rely on json decode consistency
// to determine message boundaries.
func NewStream(in io.Reader, out io.Writer) Stream {
return &plainStream{
in: json.NewDecoder(in),
out: out,
}
}
type plainStream struct {
in *json.Decoder
outMu sync.Mutex
out io.Writer
}
func (s *plainStream) Read(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
var raw json.RawMessage
if err := s.in.Decode(&raw); err != nil {
return nil, err
}
return raw, nil
}
func (s *plainStream) Write(ctx context.Context, data []byte) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
s.outMu.Lock()
_, err := s.out.Write(data)
s.outMu.Unlock()
return err
}
// NewHeaderStream returns a Stream built on top of an io.Reader and io.Writer
// The messages are sent with HTTP content length and MIME type headers.
// This is the format used by LSP and others.
func NewHeaderStream(in io.Reader, out io.Writer) Stream {
return &headerStream{
in: bufio.NewReader(in),
out: out,
}
}
type headerStream struct {
in *bufio.Reader
outMu sync.Mutex
out io.Writer
}
func (s *headerStream) Read(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
var length int64
// read the header, stop on the first empty line
for {
line, err := s.in.ReadString('\n')
if err != nil {
return nil, fmt.Errorf("failed reading header line %q", err)
}
line = strings.TrimSpace(line)
// check we have a header line
if line == "" {
break
}
colon := strings.IndexRune(line, ':')
if colon < 0 {
return nil, fmt.Errorf("invalid header line %q", line)
}
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
switch name {
case "Content-Length":
if length, err = strconv.ParseInt(value, 10, 32); err != nil {
return nil, fmt.Errorf("failed parsing Content-Length: %v", value)
}
if length <= 0 {
return nil, fmt.Errorf("invalid Content-Length: %v", length)
}
default:
// ignoring unknown headers
}
}
if length == 0 {
return nil, fmt.Errorf("missing Content-Length header")
}
data := make([]byte, length)
if _, err := io.ReadFull(s.in, data); err != nil {
return nil, err
}
return data, nil
}
func (s *headerStream) Write(ctx context.Context, data []byte) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
s.outMu.Lock()
_, err := fmt.Fprintf(s.out, "Content-Length: %v\r\n\r\n", len(data))
if err == nil {
_, err = s.out.Write(data)
}
s.outMu.Unlock()
return err
}

View File

@@ -1,139 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonrpc2
import (
"encoding/json"
"fmt"
"strconv"
)
// this file contains the go forms of the wire specification
// see http://www.jsonrpc.org/specification for details
const (
// CodeUnknownError should be used for all non coded errors.
CodeUnknownError = -32001
// CodeParseError is used when invalid JSON was received by the server.
CodeParseError = -32700
//CodeInvalidRequest is used when the JSON sent is not a valid Request object.
CodeInvalidRequest = -32600
// CodeMethodNotFound should be returned by the handler when the method does
// not exist / is not available.
CodeMethodNotFound = -32601
// CodeInvalidParams should be returned by the handler when method
// parameter(s) were invalid.
CodeInvalidParams = -32602
// CodeInternalError is not currently returned but defined for completeness.
CodeInternalError = -32603
)
// Request is sent to a server to represent a Call or Notify operaton.
type Request struct {
// VersionTag is always encoded as the string "2.0"
VersionTag VersionTag `json:"jsonrpc"`
// Method is a string containing the method name to invoke.
Method string `json:"method"`
// Params is either a struct or an array with the parameters of the method.
Params *json.RawMessage `json:"params,omitempty"`
// The id of this request, used to tie the Response back to the request.
// Will be either a string or a number. If not set, the Request is a notify,
// and no response is possible.
ID *ID `json:"id,omitempty"`
}
// Response is a reply to a Request.
// It will always have the ID field set to tie it back to a request, and will
// have either the Result or Error fields set depending on whether it is a
// success or failure response.
type Response struct {
// VersionTag is always encoded as the string "2.0"
VersionTag VersionTag `json:"jsonrpc"`
// Result is the response value, and is required on success.
Result *json.RawMessage `json:"result,omitempty"`
// Error is a structured error response if the call fails.
Error *Error `json:"error,omitempty"`
// ID must be set and is the identifier of the Request this is a response to.
ID *ID `json:"id,omitempty"`
}
// Error represents a structured error in a Response.
type Error struct {
// Code is an error code indicating the type of failure.
Code int64 `json:"code"`
// Message is a short description of the error.
Message string `json:"message"`
// Data is optional structured data containing additional information about the error.
Data *json.RawMessage `json:"data"`
}
// VersionTag is a special 0 sized struct that encodes as the jsonrpc version
// tag.
// It will fail during decode if it is not the correct version tag in the
// stream.
type VersionTag struct{}
// ID is a Request identifier.
// Only one of either the Name or Number members will be set, using the
// number form if the Name is the empty string.
type ID struct {
Name string
Number int64
}
// IsNotify returns true if this request is a notification.
func (r *Request) IsNotify() bool {
return r.ID == nil
}
func (err *Error) Error() string {
if err == nil {
return ""
}
return err.Message
}
func (VersionTag) MarshalJSON() ([]byte, error) {
return json.Marshal("2.0")
}
func (VersionTag) UnmarshalJSON(data []byte) error {
version := ""
if err := json.Unmarshal(data, &version); err != nil {
return err
}
if version != "2.0" {
return fmt.Errorf("Invalid RPC version %v", version)
}
return nil
}
// String returns a string representation of the ID.
// The representation is non ambiguous, string forms are quoted, number forms
// are preceded by a #
func (id *ID) String() string {
if id == nil {
return ""
}
if id.Name != "" {
return strconv.Quote(id.Name)
}
return "#" + strconv.FormatInt(id.Number, 10)
}
func (id *ID) MarshalJSON() ([]byte, error) {
if id.Name != "" {
return json.Marshal(id.Name)
}
return json.Marshal(id.Number)
}
func (id *ID) UnmarshalJSON(data []byte) error {
*id = ID{}
if err := json.Unmarshal(data, &id.Number); err == nil {
return nil
}
return json.Unmarshal(data, &id.Name)
}

View File

@@ -1,53 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"sort"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func toProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
var results []protocol.CompletionItem
sort.Slice(items, func(i, j int) bool {
return items[i].Score > items[j].Score
})
for _, item := range items {
results = append(results, protocol.CompletionItem{
Label: item.Label,
Detail: item.Detail,
Kind: float64(toProtocolCompletionItemKind(item.Kind)),
})
}
return results
}
func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
switch kind {
case source.InterfaceCompletionItem:
return protocol.InterfaceCompletion
case source.StructCompletionItem:
return protocol.StructCompletion
case source.TypeCompletionItem:
return protocol.TypeParameterCompletion // ??
case source.ConstantCompletionItem:
return protocol.ConstantCompletion
case source.FieldCompletionItem:
return protocol.FieldCompletion
case source.ParameterCompletionItem, source.VariableCompletionItem:
return protocol.VariableCompletion
case source.FunctionCompletionItem:
return protocol.FunctionCompletion
case source.MethodCompletionItem:
return protocol.MethodCompletion
case source.PackageCompletionItem:
return protocol.ModuleCompletion // ??
default:
return protocol.TextCompletion
}
}

View File

@@ -1,38 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func toProtocolDiagnostics(v *source.View, diagnostics []source.Diagnostic) []protocol.Diagnostic {
reports := []protocol.Diagnostic{}
for _, diag := range diagnostics {
tok := v.Config.Fset.File(diag.Range.Start)
reports = append(reports, protocol.Diagnostic{
Message: diag.Message,
Range: toProtocolRange(tok, diag.Range),
Severity: toProtocolSeverity(diag.Severity),
Source: "LSP",
})
}
return reports
}
func toProtocolSeverity(severity source.DiagnosticSeverity) protocol.DiagnosticSeverity {
switch severity {
case source.SeverityError:
return protocol.SeverityError
case source.SeverityWarning:
return protocol.SeverityWarning
case source.SeverityHint:
return protocol.SeverityHint
case source.SeverityInformation:
return protocol.SeverityInformation
}
return protocol.SeverityError // default
}

View File

@@ -1,314 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"bytes"
"context"
"fmt"
"go/token"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func TestLSP(t *testing.T) {
packagestest.TestAll(t, testLSP)
}
func testLSP(t *testing.T, exporter packagestest.Exporter) {
const dir = "testdata"
const expectedCompletionsCount = 4
const expectedDiagnosticsCount = 9
const expectedFormatCount = 3
const expectedDefinitionsCount = 16
files := packagestest.MustCopyFileTree(dir)
for fragment, operation := range files {
if trimmed := strings.TrimSuffix(fragment, ".in"); trimmed != fragment {
delete(files, fragment)
files[trimmed] = operation
}
}
modules := []packagestest.Module{
{
Name: "golang.org/x/tools/internal/lsp",
Files: files,
},
}
exported := packagestest.Export(t, exporter, modules)
defer exported.Cleanup()
dirs := make(map[string]bool)
// collect results for certain tests
expectedDiagnostics := make(diagnostics)
completionItems := make(completionItems)
expectedCompletions := make(completions)
expectedFormat := make(formats)
expectedDefinitions := make(definitions)
s := &server{
view: source.NewView(),
}
// merge the config objects
cfg := *exported.Config
cfg.Fset = s.view.Config.Fset
cfg.Mode = packages.LoadSyntax
s.view.Config = &cfg
for _, module := range modules {
for fragment := range module.Files {
if !strings.HasSuffix(fragment, ".go") {
continue
}
filename := exporter.Filename(exported, module.Name, fragment)
expectedDiagnostics[filename] = []protocol.Diagnostic{}
dirs[filepath.Dir(filename)] = true
}
}
// Do a first pass to collect special markers
if err := exported.Expect(map[string]interface{}{
"item": func(name string, r packagestest.Range, _, _ string) {
exported.Mark(name, r)
},
}); err != nil {
t.Fatal(err)
}
// Collect any data that needs to be used by subsequent tests.
if err := exported.Expect(map[string]interface{}{
"diag": expectedDiagnostics.collect,
"item": completionItems.collect,
"complete": expectedCompletions.collect,
"format": expectedFormat.collect,
"godef": expectedDefinitions.collect,
}); err != nil {
t.Fatal(err)
}
t.Run("Completion", func(t *testing.T) {
t.Helper()
if len(expectedCompletions) != expectedCompletionsCount {
t.Errorf("got %v completions expected %v", len(expectedCompletions), expectedCompletionsCount)
}
expectedCompletions.test(t, exported, s, completionItems)
})
t.Run("Diagnostics", func(t *testing.T) {
t.Helper()
diagnosticsCount := expectedDiagnostics.test(t, exported, s.view, dirs)
if diagnosticsCount != expectedDiagnosticsCount {
t.Errorf("got %v diagnostics expected %v", diagnosticsCount, expectedDiagnosticsCount)
}
})
t.Run("Format", func(t *testing.T) {
t.Helper()
if len(expectedFormat) != expectedFormatCount {
t.Errorf("got %v formats expected %v", len(expectedFormat), expectedFormatCount)
}
expectedFormat.test(t, s)
})
t.Run("Definitions", func(t *testing.T) {
t.Helper()
if len(expectedDefinitions) != expectedDefinitionsCount {
t.Errorf("got %v definitions expected %v", len(expectedDefinitions), expectedDefinitionsCount)
}
expectedDefinitions.test(t, s)
})
}
type diagnostics map[string][]protocol.Diagnostic
type completionItems map[token.Pos]*protocol.CompletionItem
type completions map[token.Position][]token.Pos
type formats map[string]string
type definitions map[protocol.Location]protocol.Location
func (c completions) test(t *testing.T, exported *packagestest.Exported, s *server, items completionItems) {
for src, itemList := range c {
var want []protocol.CompletionItem
for _, pos := range itemList {
want = append(want, *items[pos])
}
list, err := s.Completion(context.Background(), &protocol.CompletionParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.DocumentURI(source.ToURI(src.Filename)),
},
Position: protocol.Position{
Line: float64(src.Line - 1),
Character: float64(src.Column - 1),
},
},
})
if err != nil {
t.Fatal(err)
}
got := list.Items
if equal := reflect.DeepEqual(want, got); !equal {
t.Errorf("completion failed for %s:%v:%v: (expected: %v), (got: %v)", filepath.Base(src.Filename), src.Line, src.Column, want, got)
}
}
}
func (c completions) collect(src token.Position, expected []token.Pos) {
c[src] = expected
}
func (i completionItems) collect(pos token.Pos, label, detail, kind string) {
var k protocol.CompletionItemKind
switch kind {
case "struct":
k = protocol.StructCompletion
case "func":
k = protocol.FunctionCompletion
case "var":
k = protocol.VariableCompletion
case "type":
k = protocol.TypeParameterCompletion
case "field":
k = protocol.FieldCompletion
case "interface":
k = protocol.InterfaceCompletion
case "const":
k = protocol.ConstantCompletion
case "method":
k = protocol.MethodCompletion
}
i[pos] = &protocol.CompletionItem{
Label: label,
Detail: detail,
Kind: float64(k),
}
}
func (d diagnostics) test(t *testing.T, exported *packagestest.Exported, v *source.View, dirs map[string]bool) int {
// first trigger a load to get the diagnostics
var dirList []string
for dir := range dirs {
dirList = append(dirList, dir)
}
exported.Config.Mode = packages.LoadFiles
pkgs, err := packages.Load(exported.Config, dirList...)
if err != nil {
t.Fatal(err)
}
// and now see if they match the expected ones
count := 0
for _, pkg := range pkgs {
for _, filename := range pkg.GoFiles {
f := v.GetFile(source.ToURI(filename))
diagnostics, err := source.Diagnostics(context.Background(), v, f)
if err != nil {
t.Fatal(err)
}
got := toProtocolDiagnostics(v, diagnostics[filename])
sort.Slice(got, func(i int, j int) bool {
return got[i].Range.Start.Line < got[j].Range.Start.Line
})
want := d[filename]
if equal := reflect.DeepEqual(want, got); !equal {
msg := &bytes.Buffer{}
fmt.Fprintf(msg, "diagnostics failed for %s: expected:\n", filepath.Base(filename))
for _, d := range want {
fmt.Fprintf(msg, " %v\n", d)
}
fmt.Fprintf(msg, "got:\n")
for _, d := range got {
fmt.Fprintf(msg, " %v\n", d)
}
t.Error(msg.String())
}
count += len(want)
}
}
return count
}
func (d diagnostics) collect(pos token.Position, msg string) {
line := float64(pos.Line - 1)
col := float64(pos.Column - 1)
want := protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: line,
Character: col,
},
End: protocol.Position{
Line: line,
Character: col,
},
},
Severity: protocol.SeverityError,
Source: "LSP",
Message: msg,
}
d[pos.Filename] = append(d[pos.Filename], want)
}
func (f formats) test(t *testing.T, s *server) {
for filename, gofmted := range f {
edits, err := s.Formatting(context.Background(), &protocol.DocumentFormattingParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.DocumentURI(source.ToURI(filename)),
},
})
if err != nil || len(edits) == 0 {
if gofmted != "" {
t.Error(err)
}
return
}
edit := edits[0]
if edit.NewText != gofmted {
t.Errorf("formatting failed: (got: %s), (expected: %s)", edit.NewText, gofmted)
}
}
}
func (f formats) collect(pos token.Position) {
cmd := exec.Command("gofmt", pos.Filename)
stdout := bytes.NewBuffer(nil)
cmd.Stdout = stdout
cmd.Run() // ignore error, sometimes we have intentionally ungofmt-able files
f[pos.Filename] = stdout.String()
}
func (d definitions) test(t *testing.T, s *server) {
for src, target := range d {
locs, err := s.Definition(context.Background(), &protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: src.URI,
},
Position: src.Range.Start,
})
if err != nil {
t.Fatal(err)
}
if len(locs) != 1 {
t.Errorf("got %d locations for definition, expected 1", len(locs))
}
if locs[0] != target {
t.Errorf("for %v got %v want %v", src, locs[0], target)
}
}
}
func (d definitions) collect(fset *token.FileSet, src, target packagestest.Range) {
sRange := source.Range{Start: src.Start, End: src.End}
sLoc := toProtocolLocation(fset, sRange)
tRange := source.Range{Start: target.Start, End: target.End}
tLoc := toProtocolLocation(fset, tRange)
d[sLoc] = tLoc
}

View File

@@ -1,114 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"go/token"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
// fromProtocolLocation converts from a protocol location to a source range.
// It will return an error if the file of the location was not valid.
// It uses fromProtocolRange to convert the start and end positions.
func fromProtocolLocation(v *source.View, loc protocol.Location) (source.Range, error) {
f := v.GetFile(source.URI(loc.URI))
tok, err := f.GetToken()
if err != nil {
return source.Range{}, err
}
return fromProtocolRange(tok, loc.Range), nil
}
// toProtocolLocation converts from a source range back to a protocol location.
func toProtocolLocation(fset *token.FileSet, r source.Range) protocol.Location {
tokFile := fset.File(r.Start)
uri := source.ToURI(tokFile.Name())
return protocol.Location{
URI: protocol.DocumentURI(uri),
Range: toProtocolRange(tokFile, r),
}
}
// fromProtocolRange converts a protocol range to a source range.
// It uses fromProtocolPosition to convert the start and end positions, which
// requires the token file the positions belongs to.
func fromProtocolRange(f *token.File, r protocol.Range) source.Range {
start := fromProtocolPosition(f, r.Start)
var end token.Pos
switch {
case r.End == r.Start:
end = start
case r.End.Line < 0:
end = token.NoPos
default:
end = fromProtocolPosition(f, r.End)
}
return source.Range{
Start: start,
End: end,
}
}
// toProtocolRange converts from a source range back to a protocol range.
func toProtocolRange(f *token.File, r source.Range) protocol.Range {
return protocol.Range{
Start: toProtocolPosition(f, r.Start),
End: toProtocolPosition(f, r.End),
}
}
// fromProtocolPosition converts a protocol position (0-based line and column
// number) to a token.Pos (byte offset value).
// It requires the token file the pos belongs to in order to do this.
func fromProtocolPosition(f *token.File, pos protocol.Position) token.Pos {
line := lineStart(f, int(pos.Line)+1)
return line + token.Pos(pos.Character) // TODO: this is wrong, bytes not characters
}
// toProtocolPosition converts from a token pos (byte offset) to a protocol
// position (0-based line and column number)
// It requires the token file the pos belongs to in order to do this.
func toProtocolPosition(f *token.File, pos token.Pos) protocol.Position {
if !pos.IsValid() {
return protocol.Position{Line: -1.0, Character: -1.0}
}
p := f.Position(pos)
return protocol.Position{
Line: float64(p.Line - 1),
Character: float64(p.Column - 1),
}
}
// this functionality was borrowed from the analysisutil package
func lineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
//
// TODO(adonovan): eventually replace this function with the
// simpler and more efficient (*go/token.File).LineStart, added
// in go1.12.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}

View File

@@ -1,362 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "Basic JSON Structures" part of the LSP specification.
package protocol
const (
// CodeRequestCancelled is the error code that is returned when a request is
// cancelled early.
CodeRequestCancelled = -32800
)
// DocumentURI represents the URI of a document.
// Many of the interfaces contain fields that correspond to the URI of a document.
// For clarity, the type of such a field is declared as a DocumentURI.
// Over the wire, it will still be transferred as a string, but this guarantees
// that the contents of that string can be parsed as a valid URI.
type DocumentURI string
// Position in a text document expressed as zero-based line and zero-based character offset.
// A position is between two characters like an insert cursor in a editor.
type Position struct {
/**
* Line position in a document (zero-based).
*/
Line float64 `json:"line"`
/**
* Character offset on a line in a document (zero-based). Assuming that the line is
* represented as a string, the `character` value represents the gap between the
* `character` and `character + 1`.
*
* If the character value is greater than the line length it defaults back to the
* line length.
*/
Character float64 `json:"character"`
}
// Range in a text document expressed as (zero-based) start and end positions.
// A range is comparable to a selection in an editor.
// Therefore the end position is exclusive.
// If you want to specify a range that contains a line including the line
// ending character(s) then use an end position denoting the start of the next
// line.
type Range struct {
/**
* The range's start position.
*/
Start Position `json:"start"`
/**
* The range's end position.
*/
End Position `json:"end"`
}
// Location represents a location inside a resource, such as a line inside a text file.
type Location struct {
URI DocumentURI `json:"uri"`
Range Range `json:"range"`
}
// Diagnostic represents a diagnostic, such as a compiler error or warning.
// Diagnostic objects are only valid in the scope of a resource.
type Diagnostic struct {
/**
* The range at which the message applies.
*/
Range Range `json:"range"`
/**
* The diagnostic's severity. Can be omitted. If omitted it is up to the
* client to interpret diagnostics as error, warning, info or hint.
*/
Severity DiagnosticSeverity `json:"severity,omitempty"`
/**
* The diagnostic's code, which might appear in the user interface.
*/
Code string `json:"code,omitempty"` // number | string
/**
* A human-readable string describing the source of this
* diagnostic, e.g. 'typescript' or 'super lint'.
*/
Source string `json:"source,omitempty"`
/**
* The diagnostic's message.
*/
Message string `json:"message"`
/**
* An array of related diagnostic information, e.g. when symbol-names within
* a scope collide all definitions can be marked via this property.
*/
Related []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"`
}
// DiagnosticSeverity indicates the severity of a Diagnostic message.
type DiagnosticSeverity float64
const (
/**
* Reports an error.
*/
SeverityError DiagnosticSeverity = 1
/**
* Reports a warning.
*/
SeverityWarning DiagnosticSeverity = 2
/**
* Reports an information.
*/
SeverityInformation DiagnosticSeverity = 3
/**
* Reports a hint.
*/
SeverityHint DiagnosticSeverity = 4
)
// DiagnosticRelatedInformation represents a related message and source code
// location for a diagnostic.
// This should be used to point to code locations that cause or related to a
// diagnostics, e.g when duplicating a symbol in a scope.
type DiagnosticRelatedInformation struct {
/**
* The location of this related diagnostic information.
*/
Location Location `json:"location"`
/**
* The message of this related diagnostic information.
*/
Message string `json:"message"`
}
// Command represents a reference to a command.
// Provides a title which will be used to represent a command in the UI.
// Commands are identified by a string identifier.
// The protocol currently doesnt specify a set of well-known commands.
// So executing a command requires some tool extension code.
type Command struct {
/**
* Title of the command, like `save`.
*/
Title string `json:"title"`
/**
* The identifier of the actual command handler.
*/
Command string `json:"command"`
/**
* Arguments that the command handler should be
* invoked with.
*/
Arguments []interface{} `json:"arguments,omitempty"`
}
// TextEdit is a textual edit applicable to a text document.
type TextEdit struct {
/**
* The range of the text document to be manipulated. To insert
* text into a document create a range where start === end.
*/
Range Range `json:"range"`
/**
* The string to be inserted. For delete operations use an
* empty string.
*/
NewText string `json:"newText"`
}
// TextDocumentEdit describes textual changes on a single text document.
// The text document is referred to as a VersionedTextDocumentIdentifier to
// allow clients to check the text document version before an edit is applied.
// A TextDocumentEdit describes all changes on a version Si and after they are
// applied move the document to version Si+1.
// So the creator of a TextDocumentEdit doesnt need to sort the array or do
// any kind of ordering.
// However the edits must be non overlapping.
type TextDocumentEdit struct {
/**
* The text document to change.
*/
TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
/**
* The edits to be applied.
*/
Edits []TextEdit `json:"edits"`
}
// WorkspaceEdit represents changes to many resources managed in the workspace.
// The edit should either provide Changes or DocumentChanges.
// If the client can handle versioned document edits and if DocumentChanges are
// present, the latter are preferred over Changes.
type WorkspaceEdit struct {
/**
* Holds changes to existing resources.
*/
Changes map[DocumentURI][]TextEdit `json:"changes,omitempty"`
/**
* An array of `TextDocumentEdit`s to express changes to n different text documents
* where each text document edit addresses a specific version of a text document.
* Whether a client supports versioned document edits is expressed via
* `WorkspaceClientCapabilities.workspaceEdit.documentChanges`.
*/
DocumentChanges []TextDocumentEdit `json:"documentChanges,omitempty"`
}
// TextDocumentIdentifier identifies a document using a URI.
// On the protocol level, URIs are passed as strings.
// The corresponding JSON structure looks like this.
type TextDocumentIdentifier struct {
/**
* The text document's URI.
*/
URI DocumentURI `json:"uri"`
}
// TextDocumentItem is an item to transfer a text document from the client to
// the server.
type TextDocumentItem struct {
/**
* The text document's URI.
*/
URI DocumentURI `json:"uri"`
/**
* The text document's language identifier.
*/
LanguageID string `json:"languageId"`
/**
* The version number of this document (it will increase after each
* change, including undo/redo).
*/
Version float64 `json:"version"`
/**
* The content of the opened text document.
*/
Text string `json:"text"`
}
// VersionedTextDocumentIdentifier is an identifier to denote a specific version of a text document.
type VersionedTextDocumentIdentifier struct {
TextDocumentIdentifier
/**
* The version number of this document. If a versioned text document identifier
* is sent from the server to the client and the file is not open in the editor
* (the server has not received an open notification before) the server can send
* `null` to indicate that the version is known and the content on disk is the
* truth (as speced with document content ownership)
*/
Version *uint64 `json:"version"`
}
// TextDocumentPositionParams is a parameter literal used in requests to pass
// a text document and a position inside that document.
type TextDocumentPositionParams struct {
/**
* The text document.
*/
TextDocument TextDocumentIdentifier `json:"textDocument"`
/**
* The position inside the text document.
*/
Position Position `json:"position"`
}
// DocumentFilter is a document filter denotes a document through properties
// like language, scheme or pattern.
// An example is a filter that applies to TypeScript files on disk.
// Another example is a filter the applies to JSON files with name package.json:
// { language: 'typescript', scheme: 'file' }
// { language: 'json', pattern: '**/package.json' }
type DocumentFilter struct {
/**
* A language id, like `typescript`.
*/
Language string `json:"language,omitempty"`
/**
* A URI [scheme](#URI.scheme), like `file` or `untitled`.
*/
Scheme string `json:"scheme,omitempty"`
/**
* A glob pattern, like `*.{ts,js}`.
*/
Pattern string `json:"pattern,omitempty"`
}
// A document selector is the combination of one or more document filters.
type DocumentSelector []DocumentFilter
/**
* Describes the content type that a client supports in various
* result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
*
* Please note that `MarkupKinds` must not start with a `$`. This kinds
* are reserved for internal usage.
*/
type MarkupKind string
const (
/**
* Plain text is supported as a content format
*/
PlainText MarkupKind = "plaintext"
/**
* Markdown is supported as a content format
*/
Markdown MarkupKind = "markdown"
)
/**
* A `MarkupContent` literal represents a string value which content is interpreted base on its
* kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.
*
* If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.
* See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
*
* Here is an example how such a string can be constructed using JavaScript / TypeScript:
* ```ts
* let markdown: MarkdownContent = {
* kind: MarkupKind.Markdown,
* value: [
* '# Header',
* 'Some text',
* '```typescript',
* 'someCode();',
* '```'
* ].join('\n')
* };
* ```
*
* *Please Note* that clients might sanitize the return markdown. A client could decide to
* remove HTML from the markdown to avoid script execution.
*/
type MarkupContent struct {
/**
* The type of the Markup
*/
Kind MarkupKind `json:"kind"`
/**
* The content itself
*/
Value string `json:"value"`
}

View File

@@ -1,187 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protocol
import (
"context"
"encoding/json"
"golang.org/x/tools/internal/jsonrpc2"
)
type Client interface {
ShowMessage(context.Context, *ShowMessageParams) error
ShowMessageRequest(context.Context, *ShowMessageRequestParams) (*MessageActionItem, error)
LogMessage(context.Context, *LogMessageParams) error
Telemetry(context.Context, interface{}) error
RegisterCapability(context.Context, *RegistrationParams) error
UnregisterCapability(context.Context, *UnregistrationParams) error
WorkspaceFolders(context.Context) ([]WorkspaceFolder, error)
Configuration(context.Context, *ConfigurationParams) ([]interface{}, error)
ApplyEdit(context.Context, *ApplyWorkspaceEditParams) (bool, error)
PublishDiagnostics(context.Context, *PublishDiagnosticsParams) error
}
func clientHandler(client Client) jsonrpc2.Handler {
return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) {
switch r.Method {
case "$/cancelRequest":
var params CancelParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
conn.Cancel(params.ID)
case "window/showMessage":
var params ShowMessageParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(client.ShowMessage(ctx, &params))
case "window/showMessageRequest":
var params ShowMessageRequestParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := client.ShowMessageRequest(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "window/logMessage":
var params LogMessageParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(client.LogMessage(ctx, &params))
case "telemetry/event":
var params interface{}
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(client.Telemetry(ctx, &params))
case "client/registerCapability":
var params RegistrationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(client.RegisterCapability(ctx, &params))
case "client/unregisterCapability":
var params UnregistrationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(client.UnregisterCapability(ctx, &params))
case "workspace/workspaceFolders":
if r.Params != nil {
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return
}
resp, err := client.WorkspaceFolders(ctx)
unhandledError(conn.Reply(ctx, r, resp, err))
case "workspace/configuration":
var params ConfigurationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := client.Configuration(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "workspace/applyEdit":
var params ApplyWorkspaceEditParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := client.ApplyEdit(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/publishDiagnostics":
var params PublishDiagnosticsParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(client.PublishDiagnostics(ctx, &params))
default:
if r.IsNotify() {
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
}
}
}
}
type clientDispatcher struct {
*jsonrpc2.Conn
}
func (c *clientDispatcher) ShowMessage(ctx context.Context, params *ShowMessageParams) error {
return c.Conn.Notify(ctx, "window/showMessage", params)
}
func (c *clientDispatcher) ShowMessageRequest(ctx context.Context, params *ShowMessageRequestParams) (*MessageActionItem, error) {
var result MessageActionItem
if err := c.Conn.Call(ctx, "window/showMessageRequest", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *clientDispatcher) LogMessage(ctx context.Context, params *LogMessageParams) error {
return c.Conn.Notify(ctx, "window/logMessage", params)
}
func (c *clientDispatcher) Telemetry(ctx context.Context, params interface{}) error {
return c.Conn.Notify(ctx, "telemetry/event", params)
}
func (c *clientDispatcher) RegisterCapability(ctx context.Context, params *RegistrationParams) error {
return c.Conn.Notify(ctx, "client/registerCapability", params)
}
func (c *clientDispatcher) UnregisterCapability(ctx context.Context, params *UnregistrationParams) error {
return c.Conn.Notify(ctx, "client/unregisterCapability", params)
}
func (c *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFolder, error) {
var result []WorkspaceFolder
if err := c.Conn.Call(ctx, "workspace/workspaceFolders", nil, &result); err != nil {
return nil, err
}
return result, nil
}
func (c *clientDispatcher) Configuration(ctx context.Context, params *ConfigurationParams) ([]interface{}, error) {
var result []interface{}
if err := c.Conn.Call(ctx, "workspace/configuration", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (c *clientDispatcher) ApplyEdit(ctx context.Context, params *ApplyWorkspaceEditParams) (bool, error) {
var result bool
if err := c.Conn.Call(ctx, "workspace/applyEdit", params, &result); err != nil {
return false, err
}
return result, nil
}
func (c *clientDispatcher) PublishDiagnostics(ctx context.Context, params *PublishDiagnosticsParams) error {
return c.Conn.Notify(ctx, "textDocument/publishDiagnostics", params)
}

View File

@@ -1,20 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "Diagnostics" part of the LSP specification.
package protocol
type PublishDiagnosticsParams struct {
/**
* The URI for which diagnostic information is reported.
*/
URI DocumentURI `json:"uri"`
/**
* An array of diagnostic information items.
*/
Diagnostics []Diagnostic `json:"diagnostics"`
}

View File

@@ -1,16 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package protocol contains the structs that map directly to the wire format
// of the "Language Server Protocol".
//
// It is a literal transcription, with unmodified comments, and only the changes
// required to make it go code.
// Names are uppercased to export them.
// All fields have JSON tags added to correct the names.
// Fields marked with a ? are also marked as "omitempty"
// Fields that are "|| null" are made pointers
// Fields that are string or number are left as string
// Fields that are type "number" are made float64
package protocol

View File

@@ -1,849 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "General" messages part of the LSP specification.
package protocol
import "golang.org/x/tools/internal/jsonrpc2"
type CancelParams struct {
/**
* The request id to cancel.
*/
ID jsonrpc2.ID `json:"id"`
}
type InitializeParams struct {
/**
* The process Id of the parent process that started
* the server. Is null if the process has not been started by another process.
* If the parent process is not alive then the server should exit (see exit notification) its process.
*/
ProcessID *float64 `json:"processId"`
/**
* The rootPath of the workspace. Is null
* if no folder is open.
*
* @deprecated in favour of rootURI.
*/
RootPath *string `json:"rootPath"`
/**
* The rootURI of the workspace. Is null if no
* folder is open. If both `rootPath` and `rootURI` are set
* `rootURI` wins.
*/
RootURI *DocumentURI `json:"rootURI"`
/**
* User provided initialization options.
*/
InitializationOptions interface{} `json:"initializationOptions"`
/**
* The capabilities provided by the client (editor or tool)
*/
Capabilities ClientCapabilities `json:"capabilities"`
/**
* The initial trace setting. If omitted trace is disabled ('off').
*/
Trace string `json:"trace"` // 'off' | 'messages' | 'verbose'
/**
* The workspace folders configured in the client when the server starts.
* This property is only available if the client supports workspace folders.
* It can be `null` if the client supports workspace folders but none are
* configured.
*
* Since 3.6.0
*/
WorkspaceFolders []WorkspaceFolder `json:"workspaceFolders,omitempty"`
}
/**
* Workspace specific client capabilities.
*/
type WorkspaceClientCapabilities struct {
/**
* The client supports applying batch edits to the workspace by supporting
* the request 'workspace/applyEdit'
*/
ApplyEdit bool `json:"applyEdit,omitempty"`
/**
* Capabilities specific to `WorkspaceEdit`s
*/
WorkspaceEdit struct {
/**
* The client supports versioned document changes in `WorkspaceEdit`s
*/
DocumentChanges bool `json:"documentChanges,omitempty"`
} `json:"workspaceEdit,omitempty"`
/**
* Capabilities specific to the `workspace/didChangeConfiguration` notification.
*/
DidChangeConfiguration struct {
/**
* Did change configuration notification supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"didChangeConfiguration,omitempty"`
/**
* Capabilities specific to the `workspace/didChangeWatchedFiles` notification.
*/
DidChangeWatchedFiles struct {
/**
* Did change watched files notification supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"didChangeWatchedFiles,omitempty"`
/**
* Capabilities specific to the `workspace/symbol` request.
*/
Symbol struct {
/**
* Symbol request supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
*/
SymbolKind struct {
/**
* The symbol kind values the client supports. When this
* property exists the client also guarantees that it will
* handle values outside its set gracefully and falls back
* to a default value when unknown.
*
* If this property is not present the client only supports
* the symbol kinds from `File` to `Array` as defined in
* the initial version of the protocol.
*/
ValueSet []SymbolKind `json:"valueSet,omitempty"`
} `json:"symbolKind,omitempty"`
} `json:"symbol,omitempty"`
/**
* Capabilities specific to the `workspace/executeCommand` request.
*/
ExecuteCommand struct {
/**
* Execute command supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"executeCommand,omitempty"`
/**
* The client has support for workspace folders.
*
* Since 3.6.0
*/
WorkspaceFolders bool `json:"workspaceFolders,omitempty"`
/**
* The client supports `workspace/configuration` requests.
*
* Since 3.6.0
*/
Configuration bool `json:"configuration,omitempty"`
}
/**
* Text document specific client capabilities.
*/
type TextDocumentClientCapabilities struct {
Synchronization struct {
/**
* Whether text document synchronization supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* The client supports sending will save notifications.
*/
WillSave bool `json:"willSave,omitempty"`
/**
* The client supports sending a will save request and
* waits for a response providing text edits which will
* be applied to the document before it is saved.
*/
WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"`
/**
* The client supports did save notifications.
*/
DidSave bool `json:"didSave,omitempty"`
} `json:"synchronization,omitempty"`
/**
* Capabilities specific to the `textDocument/completion`
*/
Completion struct {
/**
* Whether completion supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* The client supports the following `CompletionItem` specific
* capabilities.
*/
CompletionItem struct {
/**
* Client supports snippets as insert text.
*
* A snippet can define tab stops and placeholders with `$1`, `$2`
* and `${3:foo}`. `$0` defines the final tab stop, it defaults to
* the end of the snippet. Placeholders with equal identifiers are linked,
* that is typing in one will update others too.
*/
SnippetSupport bool `json:"snippetSupport,omitempty"`
/**
* Client supports commit characters on a completion item.
*/
CommitCharactersSupport bool `json:"commitCharactersSupport,omitempty"`
/**
* Client supports the follow content formats for the documentation
* property. The order describes the preferred format of the client.
*/
DocumentationFormat []MarkupKind `json:"documentationFormat,omitempty"`
/**
* Client supports the deprecated property on a completion item.
*/
DeprecatedSupport bool `json:"deprecatedSupport,omitempty"`
/**
* Client supports the preselect property on a completion item.
*/
PreselectSupport bool `json:"preselectSupport,omitempty"`
} `json:"completionItem,omitempty"`
CompletionItemKind struct {
/**
* The completion item kind values the client supports. When this
* property exists the client also guarantees that it will
* handle values outside its set gracefully and falls back
* to a default value when unknown.
*
* If this property is not present the client only supports
* the completion items kinds from `Text` to `Reference` as defined in
* the initial version of the protocol.
*/
ValueSet []CompletionItemKind `json:"valueSet,omitempty"`
} `json:"completionItemKind,omitempty"`
/**
* The client supports to send additional context information for a
* `textDocument/completion` request.
*/
ContextSupport bool `json:"contextSupport,omitempty"`
} `json:"completion"`
/**
* Capabilities specific to the `textDocument/hover`
*/
Hover struct {
/**
* Whether hover supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* Client supports the follow content formats for the content
* property. The order describes the preferred format of the client.
*/
ContentFormat []MarkupKind `json:"contentFormat,omitempty"`
} `json:"hover,omitempty"`
/**
* Capabilities specific to the `textDocument/signatureHelp`
*/
SignatureHelp struct {
/**
* Whether signature help supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* The client supports the following `SignatureInformation`
* specific properties.
*/
SignatureInformation struct {
/**
* Client supports the follow content formats for the documentation
* property. The order describes the preferred format of the client.
*/
DocumentationFormat []MarkupKind `json:"documentationFormat,omitempty"`
} `json:"signatureInformation,omitempty"`
} `json:"signatureHelp,omitempty"`
/**
* Capabilities specific to the `textDocument/references`
*/
References struct {
/**
* Whether references supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"references,omitempty"`
/**
* Capabilities specific to the `textDocument/documentHighlight`
*/
DocumentHighlight struct {
/**
* Whether document highlight supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"documentHighlight,omitempty"`
/**
* Capabilities specific to the `textDocument/documentSymbol`
*/
DocumentSymbol struct {
/**
* Whether document symbol supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* Specific capabilities for the `SymbolKind`.
*/
SymbolKind struct {
/**
* The symbol kind values the client supports. When this
* property exists the client also guarantees that it will
* handle values outside its set gracefully and falls back
* to a default value when unknown.
*
* If this property is not present the client only supports
* the symbol kinds from `File` to `Array` as defined in
* the initial version of the protocol.
*/
ValueSet []SymbolKind `json:"valueSet,omitempty"`
} `json:"symbolKind,omitempty"`
/**
* The client support hierarchical document symbols.
*/
HierarchicalDocumentSymbolSupport bool `json:"hierarchicalDocumentSymbolSupport,omitempty"`
} `json:"documentSymbol,omitempty"`
/**
* Capabilities specific to the `textDocument/formatting`
*/
Formatting struct {
/**
* Whether formatting supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"formatting,omitempty"`
/**
* Capabilities specific to the `textDocument/rangeFormatting`
*/
RangeFormatting struct {
/**
* Whether range formatting supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"rangeFormatting,omitempty"`
/**
* Capabilities specific to the `textDocument/onTypeFormatting`
*/
OnTypeFormatting struct {
/**
* Whether on type formatting supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"onTypeFormatting,omitempty"`
/**
* Capabilities specific to the `textDocument/definition`
*/
Definition struct {
/**
* Whether definition supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"definition,omitempty"`
/**
* Capabilities specific to the `textDocument/typeDefinition`
*
* Since 3.6.0
*/
TypeDefinition struct {
/**
* Whether typeDefinition supports dynamic registration. If this is set to `true`
* the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
* return value for the corresponding server capability as well.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"typeDefinition,omitempty"`
/**
* Capabilities specific to the `textDocument/implementation`.
*
* Since 3.6.0
*/
Implementation struct {
/**
* Whether implementation supports dynamic registration. If this is set to `true`
* the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
* return value for the corresponding server capability as well.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"implementation,omitempty"`
/**
* Capabilities specific to the `textDocument/codeAction`
*/
CodeAction struct {
/**
* Whether code action supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* The client support code action literals as a valid
* response of the `textDocument/codeAction` request.
*
* Since 3.8.0
*/
CodeActionLiteralSupport struct {
/**
* The code action kind is support with the following value
* set.
*/
CodeActionKind struct {
/**
* The code action kind values the client supports. When this
* property exists the client also guarantees that it will
* handle values outside its set gracefully and falls back
* to a default value when unknown.
*/
ValueSet []CodeActionKind `json:"valueSet"`
} `json:"codeActionKind"`
} `json:"codeActionLiteralSupport,omitempty"`
} `json:"codeAction,omitempty"`
/**
* Capabilities specific to the `textDocument/codeLens`
*/
CodeLens struct {
/**
* Whether code lens supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"codeLens,omitempty"`
/**
* Capabilities specific to the `textDocument/documentLink`
*/
DocumentLink struct {
/**
* Whether document link supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"documentLink,omitempty"`
/**
* Capabilities specific to the `textDocument/documentColor` and the
* `textDocument/colorPresentation` request.
*
* Since 3.6.0
*/
ColorProvider struct {
/**
* Whether colorProvider supports dynamic registration. If this is set to `true`
* the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
* return value for the corresponding server capability as well.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"colorProvider,omitempty"`
/**
* Capabilities specific to the `textDocument/rename`
*/
Rename struct {
/**
* Whether rename supports dynamic registration.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
} `json:"rename,omitempty"`
/**
* Capabilities specific to `textDocument/publishDiagnostics`.
*/
PublishDiagnostics struct {
/**
* Whether the clients accepts diagnostics with related information.
*/
RelatedInformation bool `json:"relatedInformation,omitempty"`
} `json:"publishDiagnostics,omitempty"`
/**
* Capabilities specific to `textDocument/foldingRange` requests.
*
* Since 3.10.0
*/
FoldingRange struct {
/**
* Whether implementation supports dynamic registration for folding range providers. If this is set to `true`
* the client supports the new `(FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
* return value for the corresponding server capability as well.
*/
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
/**
* The maximum number of folding ranges that the client prefers to receive per document. The value serves as a
* hint, servers are free to follow the limit.
*/
RangeLimit float64 `json:"rangeLimit,omitempty"`
/**
* If set, the client signals that it only supports folding complete lines. If set, client will
* ignore specified `startCharacter` and `endCharacter` properties in a FoldingRange.
*/
LineFoldingOnly bool `json:"lineFoldingOnly,omitempty"`
}
}
// ClientCapabilities now define capabilities for dynamic registration, workspace
// and text document features the client supports. The experimental can be used to
// pass experimental capabilities under development. For future compatibility a
// ClientCapabilities object literal can have more properties set than currently
// defined. Servers receiving a ClientCapabilities object literal with unknown
// properties should ignore these properties. A missing property should be
// interpreted as an absence of the capability. If a property is missing that
// defines sub properties all sub properties should be interpreted as an absence
// of the capability.
//
// Client capabilities got introduced with version 3.0 of the protocol. They
// therefore only describe capabilities that got introduced in 3.x or later.
// Capabilities that existed in the 2.x version of the protocol are still
// mandatory for clients. Clients cannot opt out of providing them. So even if a
// client omits the ClientCapabilities.textDocument.synchronization it is still
// required that the client provides text document synchronization (e.g. open,
// changed and close notifications).
type ClientCapabilities struct {
/**
* Workspace specific client capabilities.
*/
Workspace WorkspaceClientCapabilities `json:"workspace,omitempty"`
/**
* Text document specific client capabilities.
*/
TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"`
/**
* Experimental client capabilities.
*/
Experimental interface{} `json:"experimental,omitempty"`
}
type InitializeResult struct {
/**
* The capabilities the language server provides.
*/
Capabilities ServerCapabilities `json:"capabilities"`
}
/**
* Defines how the host (editor) should sync document changes to the language server.
*/
type TextDocumentSyncKind float64
const (
/**
* Documents should not be synced at all.
*/
None TextDocumentSyncKind = 0
/**
* Documents are synced by always sending the full content
* of the document.
*/
Full TextDocumentSyncKind = 1
/**
* Documents are synced by sending the full content on open.
* After that only incremental updates to the document are
* send.
*/
Incremental TextDocumentSyncKind = 2
)
/**
* Completion options.
*/
type CompletionOptions struct {
/**
* The server provides support to resolve additional
* information for a completion item.
*/
ResolveProvider bool `json:"resolveProvider,omitempty"`
/**
* The characters that trigger completion automatically.
*/
TriggerCharacters []string `json:"triggerCharacters,omitempty"`
}
/**
* Signature help options.
*/
type SignatureHelpOptions struct {
/**
* The characters that trigger signature help
* automatically.
*/
TriggerCharacters []string `json:"triggerCharacters,omitempty"`
}
/**
* Code Lens options.
*/
type CodeLensOptions struct {
/**
* Code lens has a resolve provider as well.
*/
ResolveProvider bool `json:"resolveProvider,omitempty"`
}
/**
* Format document on type options.
*/
type DocumentOnTypeFormattingOptions struct {
/**
* A character on which formatting should be triggered, like `}`.
*/
FirstTriggerCharacter string `json:"firstTriggerCharacter"`
/**
* More trigger characters.
*/
MoreTriggerCharacter []string `json:"moreTriggerCharacter,omitempty"`
}
/**
* Document link options.
*/
type DocumentLinkOptions struct {
/**
* Document links have a resolve provider as well.
*/
ResolveProvider bool `json:"resolveProvider,omitempty"`
}
/**
* Execute command options.
*/
type ExecuteCommandOptions struct {
/**
* The commands to be executed on the server
*/
Commands []string `json:"commands"`
}
/**
* Save options.
*/
type SaveOptions struct {
/**
* The client is supposed to include the content on save.
*/
IncludeText bool `json:"includeText,omitempty"`
}
/**
* Color provider options.
*/
type ColorProviderOptions struct {
}
/**
* Folding range provider options.
*/
type FoldingRangeProviderOptions struct {
}
type TextDocumentSyncOptions struct {
/**
* Open and close notifications are sent to the server.
*/
OpenClose bool `json:"openClose,omitempty"`
/**
* Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full
* and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None.
*/
Change float64 `json:"change,omitempty"`
/**
* Will save notifications are sent to the server.
*/
WillSave bool `json:"willSave,omitempty"`
/**
* Will save wait until requests are sent to the server.
*/
WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"`
/**
* Save notifications are sent to the server.
*/
Save SaveOptions `json:"save,omitempty"`
}
/**
* Static registration options to be returned in the initialize request.
*/
type StaticRegistrationOptions struct {
/**
* The id used to register the request. The id can be used to deregister
* the request again. See also Registration#id.
*/
ID string `json:"id,omitempty"`
}
type ServerCapabilities struct {
/**
* Defines how text documents are synced. Is either a detailed structure defining each notification or
* for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`.
*/
TextDocumentSync interface{} `json:"textDocumentSync,omitempty"` // TextDocumentSyncOptions | number
/**
* The server provides hover support.
*/
HoverProvider bool `json:"hoverProvider,omitempty"`
/**
* The server provides completion support.
*/
CompletionProvider CompletionOptions `json:"completionProvider,omitempty"`
/**
* The server provides signature help support.
*/
SignatureHelpProvider SignatureHelpOptions `json:"signatureHelpProvider,omitempty"`
/**
* The server provides goto definition support.
*/
DefinitionProvider bool `json:"definitionProvider,omitempty"`
/**
* The server provides Goto Type Definition support.
*
* Since 3.6.0
*/
TypeDefinitionProvider interface{} `json:"typeDefinitionProvider,omitempty"` // boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions)
/**
* The server provides Goto Implementation support.
*
* Since 3.6.0
*/
ImplementationProvider interface{} `json:"implementationProvider,omitempty"` // boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions)
/**
* The server provides find references support.
*/
ReferencesProvider bool `json:"referencesProvider,omitempty"`
/**
* The server provides document highlight support.
*/
DocumentHighlightProvider bool `json:"documentHighlightProvider,omitempty"`
/**
* The server provides document symbol support.
*/
DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"`
/**
* The server provides workspace symbol support.
*/
WorkspaceSymbolProvider bool `json:"workspaceSymbolProvider,omitempty"`
/**
* The server provides code actions.
*/
CodeActionProvider bool `json:"codeActionProvider,omitempty"`
/**
* The server provides code lens.
*/
CodeLensProvider CodeLensOptions `json:"codeLensProvider,omitempty"`
/**
* The server provides document formatting.
*/
DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"`
/**
* The server provides document range formatting.
*/
DocumentRangeFormattingProvider bool `json:"documentRangeFormattingProvider,omitempty"`
/**
* The server provides document formatting on typing.
*/
DocumentOnTypeFormattingProvider DocumentOnTypeFormattingOptions `json:"documentOnTypeFormattingProvider,omitempty"`
/**
* The server provides rename support.
*/
RenameProvider bool `json:"renameProvider,omitempty"`
/**
* The server provides document link support.
*/
DocumentLinkProvider DocumentLinkOptions `json:"documentLinkProvider,omitempty"`
/**
* The server provides color provider support.
*
* Since 3.6.0
*/
//TODO: complex union type to decode here
ColorProvider interface{} `json:"colorProvider,omitempty"` // boolean | ColorProviderOptions | (ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)
/**
* The server provides folding provider support.
*
* Since 3.10.0
*/
//TODO: complex union type to decode here
FoldingRangeProvider interface{} `json:"foldingRangeProvider,omitempty"` // boolean | FoldingRangeProviderOptions | (FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)
/**
* The server provides execute command support.
*/
ExecuteCommandProvider ExecuteCommandOptions `json:"executeCommandProvider,omitempty"`
/**
* Workspace specific server capabilities
*/
Workspace struct {
/**
* The server supports workspace folder.
*
* Since 3.6.0
*/
WorkspaceFolders struct {
/**
* The server has support for workspace folders
*/
Supported bool `json:"supported,omitempty"`
/**
* Whether the server wants to receive workspace folder
* change notifications.
*
* If a strings is provided the string is treated as a ID
* under which the notification is registered on the client
* side. The ID can be used to unregister for these events
* using the `client/unregisterCapability` request.
*/
ChangeNotifications interface{} `json:"changeNotifications,omitempty"` // string | boolean
} `json:"workspaceFolders,omitempty"`
} `json:"workspace,omitempty"`
/**
* Experimental server capabilities.
*/
Experimental interface{} `json:"experimental,omitempty"`
}
type InitializedParams struct {
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains formatting functions for types that
// are commonly printed in debugging information.
// They are separated from their types and gathered here as
// they are hand written and not generated from the spec.
// They should not be relied on for programmatic use (their
// results should never be parsed for instance) but are meant
// for temporary debugging and error messages.
package protocol
import (
"fmt"
)
func (p Position) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "%d", int(p.Line)+1)
if p.Character >= 0 {
fmt.Fprintf(f, ":%d", int(p.Character)+1)
}
}
func (r Range) Format(f fmt.State, c rune) {
switch {
case r.Start == r.End || r.End.Line < 0:
fmt.Fprintf(f, "%v", r.Start)
case r.End.Line == r.Start.Line:
fmt.Fprintf(f, "%v¦%d", r.Start, int(r.End.Character)+1)
default:
fmt.Fprintf(f, "%v¦%v", r.Start, r.End)
}
}
func (l Location) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "%s:%v", l.URI, l.Range)
}
func (s DiagnosticSeverity) Format(f fmt.State, c rune) {
switch s {
case SeverityError:
fmt.Fprint(f, "Error")
case SeverityWarning:
fmt.Fprint(f, "Warning")
case SeverityInformation:
fmt.Fprint(f, "Information")
case SeverityHint:
fmt.Fprint(f, "Hint")
}
}
func (d Diagnostic) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "%v:%v from %v at %v: %v", d.Severity, d.Code, d.Source, d.Range, d.Message)
}

View File

@@ -1,49 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protocol
import (
"context"
"log"
"golang.org/x/tools/internal/jsonrpc2"
)
func canceller(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
conn.Notify(context.Background(), "$/cancelRequest", &CancelParams{ID: *req.ID})
}
func RunClient(ctx context.Context, stream jsonrpc2.Stream, client Client, opts ...interface{}) (*jsonrpc2.Conn, Server) {
opts = append([]interface{}{clientHandler(client), jsonrpc2.Canceler(canceller)}, opts...)
conn := jsonrpc2.NewConn(ctx, stream, opts...)
return conn, &serverDispatcher{Conn: conn}
}
func RunServer(ctx context.Context, stream jsonrpc2.Stream, server Server, opts ...interface{}) (*jsonrpc2.Conn, Client) {
opts = append([]interface{}{serverHandler(server), jsonrpc2.Canceler(canceller)}, opts...)
conn := jsonrpc2.NewConn(ctx, stream, opts...)
return conn, &clientDispatcher{Conn: conn}
}
func sendParseError(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request, err error) {
if _, ok := err.(*jsonrpc2.Error); !ok {
err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
}
unhandledError(conn.Reply(ctx, req, nil, err))
}
// unhandledError is used in places where an error may occur that cannot be handled.
// This occurs in things like rpc handlers that are a notify, where we cannot
// reply to the caller, or in a call when we are actually attempting to reply.
// In these cases, there is nothing we can do with the error except log it, so
// we do that in this function, and the presence of this function acts as a
// useful reminder of why we are effectively dropping the error and also a
// good place to hook in when debugging those kinds of errors.
func unhandledError(err error) {
if err == nil {
return
}
log.Printf("%v", err)
}

View File

@@ -1,61 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "Client" part of the LSP specification.
package protocol
/**
* General parameters to register for a capability.
*/
type Registration struct {
/**
* The id used to register the request. The id can be used to deregister
* the request again.
*/
ID string `json:"id"`
/**
* The method / capability to register for.
*/
Method string `json:"method"`
/**
* Options necessary for the registration.
*/
RegisterOptions interface{} `json:"registerOptions,omitempty"`
}
type RegistrationParams struct {
Registrations []Registration `json:"registrations"`
}
type TextDocumentRegistrationOptions struct {
/**
* A document selector to identify the scope of the registration. If set to null
* the document selector provided on the client side will be used.
*/
DocumentSelector *DocumentSelector `json:"documentSelector"`
}
/**
* General parameters to unregister a capability.
*/
type Unregistration struct {
/**
* The id used to unregister the request or notification. Usually an id
* provided during the register request.
*/
ID string `json:"id"`
/**
* The method / capability to unregister for.
*/
Method string `json:"method"`
}
type UnregistrationParams struct {
Unregisterations []Unregistration `json:"unregisterations"`
}

View File

@@ -1,646 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protocol
import (
"context"
"encoding/json"
"golang.org/x/tools/internal/jsonrpc2"
)
type Server interface {
Initialize(context.Context, *InitializeParams) (*InitializeResult, error)
Initialized(context.Context, *InitializedParams) error
Shutdown(context.Context) error
Exit(context.Context) error
DidChangeWorkspaceFolders(context.Context, *DidChangeWorkspaceFoldersParams) error
DidChangeConfiguration(context.Context, *DidChangeConfigurationParams) error
DidChangeWatchedFiles(context.Context, *DidChangeWatchedFilesParams) error
Symbols(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation, error)
ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{}, error)
DidOpen(context.Context, *DidOpenTextDocumentParams) error
DidChange(context.Context, *DidChangeTextDocumentParams) error
WillSave(context.Context, *WillSaveTextDocumentParams) error
WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit, error)
DidSave(context.Context, *DidSaveTextDocumentParams) error
DidClose(context.Context, *DidCloseTextDocumentParams) error
Completion(context.Context, *CompletionParams) (*CompletionList, error)
CompletionResolve(context.Context, *CompletionItem) (*CompletionItem, error)
Hover(context.Context, *TextDocumentPositionParams) (*Hover, error)
SignatureHelp(context.Context, *TextDocumentPositionParams) (*SignatureHelp, error)
Definition(context.Context, *TextDocumentPositionParams) ([]Location, error)
TypeDefinition(context.Context, *TextDocumentPositionParams) ([]Location, error)
Implementation(context.Context, *TextDocumentPositionParams) ([]Location, error)
References(context.Context, *ReferenceParams) ([]Location, error)
DocumentHighlight(context.Context, *TextDocumentPositionParams) ([]DocumentHighlight, error)
DocumentSymbol(context.Context, *DocumentSymbolParams) ([]DocumentSymbol, error)
CodeAction(context.Context, *CodeActionParams) ([]CodeAction, error)
CodeLens(context.Context, *CodeLensParams) ([]CodeLens, error)
CodeLensResolve(context.Context, *CodeLens) (*CodeLens, error)
DocumentLink(context.Context, *DocumentLinkParams) ([]DocumentLink, error)
DocumentLinkResolve(context.Context, *DocumentLink) (*DocumentLink, error)
DocumentColor(context.Context, *DocumentColorParams) ([]ColorInformation, error)
ColorPresentation(context.Context, *ColorPresentationParams) ([]ColorPresentation, error)
Formatting(context.Context, *DocumentFormattingParams) ([]TextEdit, error)
RangeFormatting(context.Context, *DocumentRangeFormattingParams) ([]TextEdit, error)
OnTypeFormatting(context.Context, *DocumentOnTypeFormattingParams) ([]TextEdit, error)
Rename(context.Context, *RenameParams) ([]WorkspaceEdit, error)
FoldingRanges(context.Context, *FoldingRangeRequestParam) ([]FoldingRange, error)
}
func serverHandler(server Server) jsonrpc2.Handler {
return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) {
switch r.Method {
case "initialize":
var params InitializeParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Initialize(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "initialized":
var params InitializedParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.Initialized(ctx, &params))
case "shutdown":
if r.Params != nil {
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return
}
unhandledError(server.Shutdown(ctx))
case "exit":
if r.Params != nil {
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
return
}
unhandledError(server.Exit(ctx))
case "$/cancelRequest":
var params CancelParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
conn.Cancel(params.ID)
case "workspace/didChangeWorkspaceFolders":
var params DidChangeWorkspaceFoldersParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidChangeWorkspaceFolders(ctx, &params))
case "workspace/didChangeConfiguration":
var params DidChangeConfigurationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidChangeConfiguration(ctx, &params))
case "workspace/didChangeWatchedFiles":
var params DidChangeWatchedFilesParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidChangeWatchedFiles(ctx, &params))
case "workspace/symbol":
var params WorkspaceSymbolParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Symbols(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "workspace/executeCommand":
var params ExecuteCommandParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.ExecuteCommand(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/didOpen":
var params DidOpenTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidOpen(ctx, &params))
case "textDocument/didChange":
var params DidChangeTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidChange(ctx, &params))
case "textDocument/willSave":
var params WillSaveTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.WillSave(ctx, &params))
case "textDocument/willSaveWaitUntil":
var params WillSaveTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.WillSaveWaitUntil(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/didSave":
var params DidSaveTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidSave(ctx, &params))
case "textDocument/didClose":
var params DidCloseTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
unhandledError(server.DidClose(ctx, &params))
case "textDocument/completion":
var params CompletionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Completion(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "completionItem/resolve":
var params CompletionItem
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.CompletionResolve(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/hover":
var params TextDocumentPositionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Hover(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/signatureHelp":
var params TextDocumentPositionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.SignatureHelp(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/definition":
var params TextDocumentPositionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Definition(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/typeDefinition":
var params TextDocumentPositionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.TypeDefinition(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/implementation":
var params TextDocumentPositionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Implementation(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/references":
var params ReferenceParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.References(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/documentHighlight":
var params TextDocumentPositionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.DocumentHighlight(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/documentSymbol":
var params DocumentSymbolParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.DocumentSymbol(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/codeAction":
var params CodeActionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.CodeAction(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/codeLens":
var params CodeLensParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.CodeLens(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "codeLens/resolve":
var params CodeLens
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.CodeLensResolve(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/documentLink":
var params DocumentLinkParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.DocumentLink(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "documentLink/resolve":
var params DocumentLink
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.DocumentLinkResolve(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/documentColor":
var params DocumentColorParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.DocumentColor(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/colorPresentation":
var params ColorPresentationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.ColorPresentation(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/formatting":
var params DocumentFormattingParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Formatting(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/rangeFormatting":
var params DocumentRangeFormattingParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.RangeFormatting(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/onTypeFormatting":
var params DocumentOnTypeFormattingParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.OnTypeFormatting(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/rename":
var params RenameParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.Rename(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
case "textDocument/foldingRanges":
var params FoldingRangeRequestParam
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, conn, r, err)
return
}
resp, err := server.FoldingRanges(ctx, &params)
unhandledError(conn.Reply(ctx, r, resp, err))
default:
if r.IsNotify() {
conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
}
}
}
}
type serverDispatcher struct {
*jsonrpc2.Conn
}
func (s *serverDispatcher) Initialize(ctx context.Context, params *InitializeParams) (*InitializeResult, error) {
var result InitializeResult
if err := s.Conn.Call(ctx, "initialize", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Initialized(ctx context.Context, params *InitializedParams) error {
return s.Conn.Notify(ctx, "initialized", params)
}
func (s *serverDispatcher) Shutdown(ctx context.Context) error {
return s.Conn.Call(ctx, "shutdown", nil, nil)
}
func (s *serverDispatcher) Exit(ctx context.Context) error {
return s.Conn.Notify(ctx, "exit", nil)
}
func (s *serverDispatcher) DidChangeWorkspaceFolders(ctx context.Context, params *DidChangeWorkspaceFoldersParams) error {
return s.Conn.Notify(ctx, "workspace/didChangeWorkspaceFolders", params)
}
func (s *serverDispatcher) DidChangeConfiguration(ctx context.Context, params *DidChangeConfigurationParams) error {
return s.Conn.Notify(ctx, "workspace/didChangeConfiguration", params)
}
func (s *serverDispatcher) DidChangeWatchedFiles(ctx context.Context, params *DidChangeWatchedFilesParams) error {
return s.Conn.Notify(ctx, "workspace/didChangeWatchedFiles", params)
}
func (s *serverDispatcher) Symbols(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation, error) {
var result []SymbolInformation
if err := s.Conn.Call(ctx, "workspace/symbol", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) ExecuteCommand(ctx context.Context, params *ExecuteCommandParams) (interface{}, error) {
var result interface{}
if err := s.Conn.Call(ctx, "workspace/executeCommand", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DidOpen(ctx context.Context, params *DidOpenTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didOpen", params)
}
func (s *serverDispatcher) DidChange(ctx context.Context, params *DidChangeTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didChange", params)
}
func (s *serverDispatcher) WillSave(ctx context.Context, params *WillSaveTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/willSave", params)
}
func (s *serverDispatcher) WillSaveWaitUntil(ctx context.Context, params *WillSaveTextDocumentParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/willSaveWaitUntil", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DidSave(ctx context.Context, params *DidSaveTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didSave", params)
}
func (s *serverDispatcher) DidClose(ctx context.Context, params *DidCloseTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didClose", params)
}
func (s *serverDispatcher) Completion(ctx context.Context, params *CompletionParams) (*CompletionList, error) {
var result CompletionList
if err := s.Conn.Call(ctx, "textDocument/completion", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) CompletionResolve(ctx context.Context, params *CompletionItem) (*CompletionItem, error) {
var result CompletionItem
if err := s.Conn.Call(ctx, "completionItem/resolve", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Hover(ctx context.Context, params *TextDocumentPositionParams) (*Hover, error) {
var result Hover
if err := s.Conn.Call(ctx, "textDocument/hover", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) SignatureHelp(ctx context.Context, params *TextDocumentPositionParams) (*SignatureHelp, error) {
var result SignatureHelp
if err := s.Conn.Call(ctx, "textDocument/signatureHelp", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Definition(ctx context.Context, params *TextDocumentPositionParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/definition", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) TypeDefinition(ctx context.Context, params *TextDocumentPositionParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/typeDefinition", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Implementation(ctx context.Context, params *TextDocumentPositionParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/implementation", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) References(ctx context.Context, params *ReferenceParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/references", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DocumentHighlight(ctx context.Context, params *TextDocumentPositionParams) ([]DocumentHighlight, error) {
var result []DocumentHighlight
if err := s.Conn.Call(ctx, "textDocument/documentHighlight", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DocumentSymbol(ctx context.Context, params *DocumentSymbolParams) ([]DocumentSymbol, error) {
var result []DocumentSymbol
if err := s.Conn.Call(ctx, "textDocument/documentSymbol", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) CodeAction(ctx context.Context, params *CodeActionParams) ([]CodeAction, error) {
var result []CodeAction
if err := s.Conn.Call(ctx, "textDocument/codeAction", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) CodeLens(ctx context.Context, params *CodeLensParams) ([]CodeLens, error) {
var result []CodeLens
if err := s.Conn.Call(ctx, "textDocument/codeLens", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) CodeLensResolve(ctx context.Context, params *CodeLens) (*CodeLens, error) {
var result CodeLens
if err := s.Conn.Call(ctx, "codeLens/resolve", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) DocumentLink(ctx context.Context, params *DocumentLinkParams) ([]DocumentLink, error) {
var result []DocumentLink
if err := s.Conn.Call(ctx, "textDocument/documentLink", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DocumentLinkResolve(ctx context.Context, params *DocumentLink) (*DocumentLink, error) {
var result DocumentLink
if err := s.Conn.Call(ctx, "documentLink/resolve", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) DocumentColor(ctx context.Context, params *DocumentColorParams) ([]ColorInformation, error) {
var result []ColorInformation
if err := s.Conn.Call(ctx, "textDocument/documentColor", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) ColorPresentation(ctx context.Context, params *ColorPresentationParams) ([]ColorPresentation, error) {
var result []ColorPresentation
if err := s.Conn.Call(ctx, "textDocument/colorPresentation", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Formatting(ctx context.Context, params *DocumentFormattingParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/formatting", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) RangeFormatting(ctx context.Context, params *DocumentRangeFormattingParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/rangeFormatting", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) OnTypeFormatting(ctx context.Context, params *DocumentOnTypeFormattingParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/onTypeFormatting", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Rename(ctx context.Context, params *RenameParams) ([]WorkspaceEdit, error) {
var result []WorkspaceEdit
if err := s.Conn.Call(ctx, "textDocument/rename", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) FoldingRanges(ctx context.Context, params *FoldingRangeRequestParam) ([]FoldingRange, error) {
var result []FoldingRange
if err := s.Conn.Call(ctx, "textDocument/foldingRanges", params, &result); err != nil {
return nil, err
}
return result, nil
}

View File

@@ -1,130 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "Text Synchronization" part of the LSP specification.
package protocol
type DidOpenTextDocumentParams struct {
/**
* The document that was opened.
*/
TextDocument TextDocumentItem `json:"textDocument"`
}
type DidChangeTextDocumentParams struct {
/**
* The document that did change. The version number points
* to the version after all provided content changes have
* been applied.
*/
TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
/**
* The actual content changes. The content changes describe single state changes
* to the document. So if there are two content changes c1 and c2 for a document
* in state S10 then c1 move the document to S11 and c2 to S12.
*/
ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"`
}
/**
* An event describing a change to a text document. If range and rangeLength are omitted
* the new text is considered to be the full content of the document.
*/
type TextDocumentContentChangeEvent struct {
/**
* The range of the document that changed.
*/
Range Range `json:"range,omitempty"`
/**
* The length of the range that got replaced.
*/
RangeLength float64 `json:"rangeLength,omitempty"`
/**
* The new text of the range/document.
*/
Text string `json:"text"`
}
/**
* Describe options to be used when registering for text document change events.
*/
type TextDocumentChangeRegistrationOptions struct {
TextDocumentRegistrationOptions
/**
* How documents are synced to the server. See TextDocumentSyncKind.Full
* and TextDocumentSyncKind.Incremental.
*/
SyncKind float64 `json:"syncKind"`
}
/**
* The parameters send in a will save text document notification.
*/
type WillSaveTextDocumentParams struct {
/**
* The document that will be saved.
*/
TextDocument TextDocumentIdentifier `json:"textDocument"`
/**
* The 'TextDocumentSaveReason'.
*/
Reason TextDocumentSaveReason `json:"reason"`
}
/**
* Represents reasons why a text document is saved.
*/
type TextDocumentSaveReason float64
const (
/**
* Manually triggered, e.g. by the user pressing save, by starting debugging,
* or by an API call.
*/
Manual TextDocumentSaveReason = 1
/**
* Automatic after a delay.
*/
AfterDelay TextDocumentSaveReason = 2
/**
* When the editor lost focus.
*/
FocusOut TextDocumentSaveReason = 3
)
type DidSaveTextDocumentParams struct {
/**
* The document that was saved.
*/
TextDocument TextDocumentIdentifier `json:"textDocument"`
/**
* Optional the content when saved. Depends on the includeText value
* when the save notification was requested.
*/
Text string `json:"text,omitempty"`
}
type TextDocumentSaveRegistrationOptions struct {
TextDocumentRegistrationOptions
/**
* The client is supposed to include the content on save.
*/
IncludeText bool `json:"includeText,omitempty"`
}
type DidCloseTextDocumentParams struct {
/**
* The document that was closed.
*/
TextDocument TextDocumentIdentifier `json:"textDocument"`
}

View File

@@ -1,77 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "Window" messages part of the LSP specification.
package protocol
type ShowMessageParams struct {
/**
* The message type. See {@link MessageType}.
*/
Type MessageType `json:"type"`
/**
* The actual message.
*/
Message string `json:"message"`
}
type MessageType float64
const (
/**
* An error message.
*/
Error MessageType = 1
/**
* A warning message.
*/
Warning MessageType = 2
/**
* An information message.
*/
Info MessageType = 3
/**
* A log message.
*/
Log MessageType = 4
)
type ShowMessageRequestParams struct {
/**
* The message type. See {@link MessageType}.
*/
Type MessageType `json:"type"`
/**
* The actual message.
*/
Message string `json:"message"`
/**
* The message action items to present.
*/
Actions []MessageActionItem `json:"actions,omitempty"`
}
type MessageActionItem struct {
/**
* A short title like 'Retry', 'Open Log' etc.
*/
Title string
}
type LogMessageParams struct {
/**
* The message type. See {@link MessageType}.
*/
Type MessageType `json:"type"`
/**
* The actual message.
*/
Message string `json:"message"`
}

View File

@@ -1,203 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the corresponding structures to the
// "Workspace" part of the LSP specification.
package protocol
type WorkspaceFolder struct {
/**
* The associated URI for this workspace folder.
*/
URI string `json:"uri"`
/**
* The name of the workspace folder. Defaults to the
* uri's basename.
*/
Name string `json:"name"`
}
type DidChangeWorkspaceFoldersParams struct {
/**
* The actual workspace folder change event.
*/
Event WorkspaceFoldersChangeEvent `json:"event"`
}
/**
* The workspace folder change event.
*/
type WorkspaceFoldersChangeEvent struct {
/**
* The array of added workspace folders
*/
Added []WorkspaceFolder `json:"added"`
/**
* The array of the removed workspace folders
*/
Removed []WorkspaceFolder `json:"removed"`
}
type DidChangeConfigurationParams struct {
/**
* The actual changed settings
*/
Settings interface{} `json:"settings"`
}
type ConfigurationParams struct {
Items []ConfigurationItem `json:"items"`
}
type ConfigurationItem struct {
/**
* The scope to get the configuration section for.
*/
ScopeURI string `json:"scopeURI,omitempty"`
/**
* The configuration section asked for.
*/
Section string `json:"section,omitempty"`
}
type DidChangeWatchedFilesParams struct {
/**
* The actual file events.
*/
Changes []FileEvent `json:"changes"`
}
/**
* An event describing a file change.
*/
type FileEvent struct {
/**
* The file's URI.
*/
URI DocumentURI `json:"uri"`
/**
* The change type.
*/
Type float64 `json:"type"`
}
/**
* The file event type.
*/
type FileChangeType float64
const (
/**
* The file got created.
*/
Created FileChangeType = 1
/**
* The file got changed.
*/
Changed FileChangeType = 2
/**
* The file got deleted.
*/
Deleted FileChangeType = 3
)
/**
* Describe options to be used when registering for text document change events.
*/
type DidChangeWatchedFilesRegistrationOptions struct {
/**
* The watchers to register.
*/
Watchers []FileSystemWatcher `json:"watchers"`
}
type FileSystemWatcher struct {
/**
* The glob pattern to watch
*/
GlobPattern string `json:"globPattern"`
/**
* The kind of events of interest. If omitted it defaults
* to WatchKind.Create | WatchKind.Change | WatchKind.Delete
* which is 7.
*/
Kind float64 `json:"kind,omitempty"`
}
type WatchKind float64
const (
/**
* Interested in create events.
*/
Create WatchKind = 1
/**
* Interested in change events
*/
Change WatchKind = 2
/**
* Interested in delete events
*/
Delete WatchKind = 4
)
/**
* The parameters of a Workspace Symbol Request.
*/
type WorkspaceSymbolParams struct {
/**
* A non-empty query string
*/
Query string `json:"query"`
}
type ExecuteCommandParams struct {
/**
* The identifier of the actual command handler.
*/
Command string `json:"command"`
/**
* Arguments that the command should be invoked with.
*/
Arguments []interface{} `json:"arguments,omitempty"`
}
/**
* Execute command registration options.
*/
type ExecuteCommandRegistrationOptions struct {
/**
* The commands to be executed on the server
*/
Commands []string `json:"commands"`
}
type ApplyWorkspaceEditParams struct {
/**
* An optional label of the workspace edit. This label is
* presented in the user interface for example on an undo
* stack to undo the workspace edit.
*/
Label string `json:"label,omitempty"`
/**
* The edits to apply.
*/
Edit WorkspaceEdit `json:"edit"`
}
type ApplyWorkspaceEditResponse struct {
/**
* Indicates whether the edit was applied or not.
*/
Applied bool `json:"applied"`
}

View File

@@ -1,315 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"context"
"go/token"
"os"
"sync"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
// RunServer starts an LSP server on the supplied stream, and waits until the
// stream is closed.
func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) error {
s := &server{}
conn, client := protocol.RunServer(ctx, stream, s, opts...)
s.client = client
return conn.Wait(ctx)
}
type server struct {
client protocol.Client
initializedMu sync.Mutex
initialized bool // set once the server has received "initialize" request
view *source.View
}
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
s.initializedMu.Lock()
defer s.initializedMu.Unlock()
if s.initialized {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
}
s.view = source.NewView()
s.initialized = true
return &protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
CompletionProvider: protocol.CompletionOptions{
TriggerCharacters: []string{"."},
},
DefinitionProvider: true,
DocumentFormattingProvider: true,
DocumentRangeFormattingProvider: true,
SignatureHelpProvider: protocol.SignatureHelpOptions{
TriggerCharacters: []string{"("},
},
TextDocumentSync: protocol.TextDocumentSyncOptions{
Change: float64(protocol.Full), // full contents of file sent on each update
OpenClose: true,
},
},
}, nil
}
func (s *server) Initialized(context.Context, *protocol.InitializedParams) error {
return nil // ignore
}
func (s *server) Shutdown(context.Context) error {
s.initializedMu.Lock()
defer s.initializedMu.Unlock()
if !s.initialized {
return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
}
s.initialized = false
return nil
}
func (s *server) Exit(ctx context.Context) error {
if s.initialized {
os.Exit(1)
}
os.Exit(0)
return nil
}
func (s *server) DidChangeWorkspaceFolders(context.Context, *protocol.DidChangeWorkspaceFoldersParams) error {
return notImplemented("DidChangeWorkspaceFolders")
}
func (s *server) DidChangeConfiguration(context.Context, *protocol.DidChangeConfigurationParams) error {
return notImplemented("DidChangeConfiguration")
}
func (s *server) DidChangeWatchedFiles(context.Context, *protocol.DidChangeWatchedFilesParams) error {
return notImplemented("DidChangeWatchedFiles")
}
func (s *server) Symbols(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
return nil, notImplemented("Symbols")
}
func (s *server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) {
return nil, notImplemented("ExecuteCommand")
}
func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
s.cacheAndDiagnoseFile(ctx, params.TextDocument.URI, params.TextDocument.Text)
return nil
}
func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
if len(params.ContentChanges) < 1 {
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided")
}
// We expect the full content of file, i.e. a single change with no range.
if change := params.ContentChanges[0]; change.RangeLength == 0 {
s.cacheAndDiagnoseFile(ctx, params.TextDocument.URI, change.Text)
}
return nil
}
func (s *server) cacheAndDiagnoseFile(ctx context.Context, uri protocol.DocumentURI, text string) {
f := s.view.GetFile(source.URI(uri))
f.SetContent([]byte(text))
go func() {
f := s.view.GetFile(source.URI(uri))
reports, err := source.Diagnostics(ctx, s.view, f)
if err != nil {
return // handle error?
}
for filename, diagnostics := range reports {
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
URI: protocol.DocumentURI(source.ToURI(filename)),
Diagnostics: toProtocolDiagnostics(s.view, diagnostics),
})
}
}()
}
func (s *server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error {
return notImplemented("WillSave")
}
func (s *server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) {
return nil, notImplemented("WillSaveWaitUntil")
}
func (s *server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) error {
// TODO(rstambler): Should we clear the cache here?
return nil // ignore
}
func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
s.view.GetFile(source.URI(params.TextDocument.URI)).SetContent(nil)
return nil
}
func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
f := s.view.GetFile(source.URI(params.TextDocument.URI))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
items, err := source.Completion(ctx, f, pos)
if err != nil {
return nil, err
}
return &protocol.CompletionList{
IsIncomplete: false,
Items: toProtocolCompletionItems(items),
}, nil
}
func (s *server) CompletionResolve(context.Context, *protocol.CompletionItem) (*protocol.CompletionItem, error) {
return nil, notImplemented("CompletionResolve")
}
func (s *server) Hover(context.Context, *protocol.TextDocumentPositionParams) (*protocol.Hover, error) {
return nil, notImplemented("Hover")
}
func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) {
f := s.view.GetFile(source.URI(params.TextDocument.URI))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
info, err := source.SignatureHelp(ctx, f, pos)
if err != nil {
return nil, err
}
return toProtocolSignatureHelp(info), nil
}
func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
f := s.view.GetFile(source.URI(params.TextDocument.URI))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
r, err := source.Definition(ctx, f, pos)
if err != nil {
return nil, err
}
return []protocol.Location{toProtocolLocation(s.view.Config.Fset, r)}, nil
}
func (s *server) TypeDefinition(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
return nil, notImplemented("TypeDefinition")
}
func (s *server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
return nil, notImplemented("Implementation")
}
func (s *server) References(context.Context, *protocol.ReferenceParams) ([]protocol.Location, error) {
return nil, notImplemented("References")
}
func (s *server) DocumentHighlight(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) {
return nil, notImplemented("DocumentHighlight")
}
func (s *server) DocumentSymbol(context.Context, *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) {
return nil, notImplemented("DocumentSymbol")
}
func (s *server) CodeAction(context.Context, *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
return nil, notImplemented("CodeAction")
}
func (s *server) CodeLens(context.Context, *protocol.CodeLensParams) ([]protocol.CodeLens, error) {
return nil, nil // ignore
}
func (s *server) CodeLensResolve(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) {
return nil, notImplemented("CodeLensResolve")
}
func (s *server) DocumentLink(context.Context, *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
return nil, nil // ignore
}
func (s *server) DocumentLinkResolve(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) {
return nil, notImplemented("DocumentLinkResolve")
}
func (s *server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) {
return nil, notImplemented("DocumentColor")
}
func (s *server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) {
return nil, notImplemented("ColorPresentation")
}
func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
return formatRange(ctx, s.view, params.TextDocument.URI, nil)
}
func (s *server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) {
return formatRange(ctx, s.view, params.TextDocument.URI, &params.Range)
}
// formatRange formats a document with a given range.
func formatRange(ctx context.Context, v *source.View, uri protocol.DocumentURI, rng *protocol.Range) ([]protocol.TextEdit, error) {
f := v.GetFile(source.URI(uri))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
var r source.Range
if rng == nil {
r.Start = tok.Pos(0)
r.End = tok.Pos(tok.Size())
} else {
r = fromProtocolRange(tok, *rng)
}
edits, err := source.Format(ctx, f, r)
if err != nil {
return nil, err
}
return toProtocolEdits(tok, edits), nil
}
func toProtocolEdits(f *token.File, edits []source.TextEdit) []protocol.TextEdit {
if edits == nil {
return nil
}
result := make([]protocol.TextEdit, len(edits))
for i, edit := range edits {
result[i] = protocol.TextEdit{
Range: toProtocolRange(f, edit.Range),
NewText: edit.NewText,
}
}
return result
}
func (s *server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) {
return nil, notImplemented("OnTypeFormatting")
}
func (s *server) Rename(context.Context, *protocol.RenameParams) ([]protocol.WorkspaceEdit, error) {
return nil, notImplemented("Rename")
}
func (s *server) FoldingRanges(context.Context, *protocol.FoldingRangeRequestParam) ([]protocol.FoldingRange, error) {
return nil, notImplemented("FoldingRanges")
}
func notImplemented(method string) *jsonrpc2.Error {
return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method)
}

View File

@@ -1,33 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func toProtocolSignatureHelp(info *source.SignatureInformation) *protocol.SignatureHelp {
return &protocol.SignatureHelp{
ActiveParameter: float64(info.ActiveParameter),
ActiveSignature: 0, // there is only ever one possible signature
Signatures: []protocol.SignatureInformation{
{
Label: info.Label,
Parameters: toProtocolParameterInformation(info.Parameters),
},
},
}
}
func toProtocolParameterInformation(info []source.ParameterInformation) []protocol.ParameterInformation {
var result []protocol.ParameterInformation
for _, p := range info {
result = append(result, protocol.ParameterInformation{
Label: p.Label,
})
}
return result
}

View File

@@ -1,734 +0,0 @@
package source
import (
"bytes"
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/ast/astutil"
)
type CompletionItem struct {
Label, Detail string
Kind CompletionItemKind
Score int
}
type CompletionItemKind int
const (
Unknown CompletionItemKind = iota
InterfaceCompletionItem
StructCompletionItem
TypeCompletionItem
ConstantCompletionItem
FieldCompletionItem
ParameterCompletionItem
VariableCompletionItem
FunctionCompletionItem
MethodCompletionItem
PackageCompletionItem
)
func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, error) {
file, err := f.GetAST()
if err != nil {
return nil, err
}
pkg, err := f.GetPackage()
if err != nil {
return nil, err
}
items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
return items, err
}
type finder func(types.Object, float64, []CompletionItem) []CompletionItem
// completions returns the map of possible candidates for completion, given a
// position, a file AST, and type information. The prefix is computed based on
// the preceding identifier and can be used by the client to score the quality
// of the completion. For instance, some clients may tolerate imperfect matches
// as valid completion results, since users may make typos.
func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.Package, info *types.Info) (items []CompletionItem, prefix string, err error) {
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if path == nil {
return nil, "", fmt.Errorf("cannot find node enclosing position")
}
// If the position is not an identifier but immediately follows
// an identifier or selector period (as is common when
// requesting a completion), use the path to the preceding node.
if _, ok := path[0].(*ast.Ident); !ok {
if p, _ := astutil.PathEnclosingInterval(file, pos-1, pos-1); p != nil {
switch p[0].(type) {
case *ast.Ident, *ast.SelectorExpr:
path = p // use preceding ident/selector
}
}
}
// Save certain facts about the query position, including the expected type
// of the completion result, the signature of the function enclosing the
// position.
typ := expectedType(path, pos, info)
sig := enclosingFunction(path, pos, info)
pkgStringer := qualifier(file, pkg, info)
seen := make(map[types.Object]bool)
// found adds a candidate completion.
// Only the first candidate of a given name is considered.
found := func(obj types.Object, weight float64, items []CompletionItem) []CompletionItem {
if obj.Pkg() != nil && obj.Pkg() != pkg && !obj.Exported() {
return items // inaccessible
}
if !seen[obj] {
seen[obj] = true
if typ != nil && matchingTypes(typ, obj.Type()) {
weight *= 10
}
if !strings.HasPrefix(obj.Name(), prefix) {
return items
}
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
return isParameter(sig, v)
})
items = append(items, item)
}
return items
}
// The position is within a composite literal.
if items, ok := complit(path, pos, pkg, info, found); ok {
return items, "", nil
}
switch n := path[0].(type) {
case *ast.Ident:
// Set the filter prefix.
prefix = n.Name[:pos-n.Pos()]
// Is this the Sel part of a selector?
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
return selector(sel, info, found)
}
// reject defining identifiers
if obj, ok := info.Defs[n]; ok {
if v, ok := obj.(*types.Var); ok && v.IsField() {
// An anonymous field is also a reference to a type.
} else {
of := ""
if obj != nil {
qual := types.RelativeTo(pkg)
of += ", of " + types.ObjectString(obj, qual)
}
return nil, "", fmt.Errorf("this is a definition%s", of)
}
}
items = append(items, lexical(path, pos, pkg, info, found)...)
// The function name hasn't been typed yet, but the parens are there:
// recv.‸(arg)
case *ast.TypeAssertExpr:
// Create a fake selector expression.
return selector(&ast.SelectorExpr{X: n.X}, info, found)
case *ast.SelectorExpr:
return selector(n, info, found)
default:
// fallback to lexical completions
return lexical(path, pos, pkg, info, found), "", nil
}
return items, prefix, nil
}
// selector finds completions for
// the specified selector expression.
// TODO(rstambler): Set the prefix filter correctly for selectors.
func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []CompletionItem, prefix string, err error) {
// Is sel a qualified identifier?
if id, ok := sel.X.(*ast.Ident); ok {
if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
// Enumerate package members.
// TODO(adonovan): can Imported() be nil?
scope := pkgname.Imported().Scope()
// TODO testcase: bad import
for _, name := range scope.Names() {
items = found(scope.Lookup(name), 1, items)
}
return items, prefix, nil
}
}
// Inv: sel is a true selector.
tv, ok := info.Types[sel.X]
if !ok {
return nil, "", fmt.Errorf("cannot resolve %s", sel.X)
}
// methods of T
mset := types.NewMethodSet(tv.Type)
for i := 0; i < mset.Len(); i++ {
items = found(mset.At(i).Obj(), 1, items)
}
// methods of *T
if tv.Addressable() && !types.IsInterface(tv.Type) && !isPointer(tv.Type) {
mset := types.NewMethodSet(types.NewPointer(tv.Type))
for i := 0; i < mset.Len(); i++ {
items = found(mset.At(i).Obj(), 1, items)
}
}
// fields of T
for _, f := range fieldSelections(tv.Type) {
items = found(f, 1, items)
}
return items, prefix, nil
}
// lexical finds completions in the lexical environment.
func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem) {
var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
for _, n := range path {
switch node := n.(type) {
case *ast.FuncDecl:
n = node.Type
case *ast.FuncLit:
n = node.Type
}
scopes = append(scopes, info.Scopes[n])
}
scopes = append(scopes, pkg.Scope())
// Process scopes innermost first.
for i, scope := range scopes {
if scope == nil {
continue
}
for _, name := range scope.Names() {
declScope, obj := scope.LookupParent(name, pos)
if declScope != scope {
continue // Name was declared in some enclosing scope, or not at all.
}
// If obj's type is invalid, find the AST node that defines the lexical block
// containing the declaration of obj. Don't resolve types for packages.
if _, ok := obj.(*types.PkgName); !ok && obj.Type() == types.Typ[types.Invalid] {
// Match the scope to its ast.Node. If the scope is the package scope,
// use the *ast.File as the starting node.
var node ast.Node
if i < len(path) {
node = path[i]
} else if i == len(path) { // use the *ast.File for package scope
node = path[i-1]
}
if node != nil {
if resolved := resolveInvalid(obj, node, info); resolved != nil {
obj = resolved
}
}
}
score := 1.0
// Rank builtins significantly lower than other results.
if scope == types.Universe {
score *= 0.1
}
items = found(obj, score, items)
}
}
return items
}
// complit finds completions for field names inside a composite literal.
// It reports whether the node was handled as part of a composite literal.
func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem, ok bool) {
var lit *ast.CompositeLit
// First, determine if the pos is within a composite literal.
switch n := path[0].(type) {
case *ast.CompositeLit:
// The enclosing node will be a composite literal if the user has just
// opened the curly brace (e.g. &x{<>) or the completion request is triggered
// from an already completed composite literal expression (e.g. &x{foo: 1, <>})
//
// If the cursor position is within a key-value expression inside the composite
// literal, we try to determine if it is before or after the colon. If it is before
// the colon, we return field completions. If the cursor does not belong to any
// expression within the composite literal, we show composite literal completions.
var expr ast.Expr
for _, e := range n.Elts {
if e.Pos() <= pos && pos < e.End() {
expr = e
break
}
}
lit = n
// If the position belongs to a key-value expression and is after the colon,
// don't show composite literal completions.
if kv, ok := expr.(*ast.KeyValueExpr); ok && pos > kv.Colon {
lit = nil
}
case *ast.KeyValueExpr:
// If the enclosing node is a key-value expression (e.g. &x{foo: <>}),
// we show composite literal completions if the cursor position is before the colon.
if len(path) > 1 && pos < n.Colon {
if l, ok := path[1].(*ast.CompositeLit); ok {
lit = l
}
}
case *ast.Ident:
// If the enclosing node is an identifier, it can either be an identifier that is
// part of a composite literal (e.g. &x{fo<>}), or it can be an identifier that is
// part of a key-value expression, which is part of a composite literal (e.g. &x{foo: ba<>).
// We handle both of these cases, showing composite literal completions only if
// the cursor position for the key-value expression is before the colon.
if len(path) > 1 {
if l, ok := path[1].(*ast.CompositeLit); ok {
lit = l
} else if len(path) > 2 {
if l, ok := path[2].(*ast.CompositeLit); ok {
// Confirm that cursor position is inside curly braces.
if l.Lbrace <= pos && pos <= l.Rbrace {
lit = l
if kv, ok := path[1].(*ast.KeyValueExpr); ok {
if pos > kv.Colon {
lit = nil
}
}
}
}
}
}
}
// We are not in a composite literal.
if lit == nil {
return nil, false
}
// Mark fields of the composite literal that have already been set,
// except for the current field.
hasKeys := false // true if the composite literal already has key-value pairs
addedFields := make(map[*types.Var]bool)
for _, el := range lit.Elts {
if kv, ok := el.(*ast.KeyValueExpr); ok {
hasKeys = true
if kv.Pos() <= pos && pos <= kv.End() {
continue
}
if key, ok := kv.Key.(*ast.Ident); ok {
if used, ok := info.Uses[key]; ok {
if usedVar, ok := used.(*types.Var); ok {
addedFields[usedVar] = true
}
}
}
}
}
// If the underlying type of the composite literal is a struct,
// collect completions for the fields of this struct.
if tv, ok := info.Types[lit]; ok {
var structPkg *types.Package // package containing the struct type declaration
if s, ok := tv.Type.Underlying().(*types.Struct); ok {
for i := 0; i < s.NumFields(); i++ {
field := s.Field(i)
if i == 0 {
structPkg = field.Pkg()
}
if !addedFields[field] {
items = found(field, 10, items)
}
}
// Add lexical completions if the user hasn't typed a key value expression
// and if the struct fields are defined in the same package as the user is in.
if !hasKeys && structPkg == pkg {
items = append(items, lexical(path, pos, pkg, info, found)...)
}
return items, true
}
}
return items, false
}
// formatCompletion creates a completion item for a given types.Object.
func formatCompletion(obj types.Object, qualifier types.Qualifier, score float64, isParam func(*types.Var) bool) CompletionItem {
label := obj.Name()
detail := types.TypeString(obj.Type(), qualifier)
var kind CompletionItemKind
switch o := obj.(type) {
case *types.TypeName:
detail, kind = formatType(o.Type(), qualifier)
if obj.Parent() == types.Universe {
detail = ""
}
case *types.Const:
if obj.Parent() == types.Universe {
detail = ""
} else {
val := o.Val().ExactString()
if !strings.Contains(val, "\\n") { // skip any multiline constants
label += " = " + o.Val().ExactString()
}
}
kind = ConstantCompletionItem
case *types.Var:
if _, ok := o.Type().(*types.Struct); ok {
detail = "struct{...}" // for anonymous structs
}
if o.IsField() {
kind = FieldCompletionItem
} else if isParam(o) {
kind = ParameterCompletionItem
} else {
kind = VariableCompletionItem
}
case *types.Func:
if sig, ok := o.Type().(*types.Signature); ok {
label += formatParams(sig.Params(), sig.Variadic(), qualifier)
detail = strings.Trim(types.TypeString(sig.Results(), qualifier), "()")
kind = FunctionCompletionItem
if sig.Recv() != nil {
kind = MethodCompletionItem
}
}
case *types.Builtin:
item, ok := builtinDetails[obj.Name()]
if !ok {
break
}
label, detail = item.label, item.detail
kind = FunctionCompletionItem
case *types.PkgName:
kind = PackageCompletionItem
detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
case *types.Nil:
kind = VariableCompletionItem
detail = ""
}
detail = strings.TrimPrefix(detail, "untyped ")
return CompletionItem{
Label: label,
Detail: detail,
Kind: kind,
}
}
// formatType returns the detail and kind for an object of type *types.TypeName.
func formatType(typ types.Type, qualifier types.Qualifier) (detail string, kind CompletionItemKind) {
if types.IsInterface(typ) {
detail = "interface{...}"
kind = InterfaceCompletionItem
} else if _, ok := typ.(*types.Struct); ok {
detail = "struct{...}"
kind = StructCompletionItem
} else if typ != typ.Underlying() {
detail, kind = formatType(typ.Underlying(), qualifier)
} else {
detail = types.TypeString(typ, qualifier)
kind = TypeCompletionItem
}
return detail, kind
}
// formatParams correctly format the parameters of a function.
func formatParams(t *types.Tuple, variadic bool, qualifier types.Qualifier) string {
var b bytes.Buffer
b.WriteByte('(')
for i := 0; i < t.Len(); i++ {
if i > 0 {
b.WriteString(", ")
}
el := t.At(i)
typ := types.TypeString(el.Type(), qualifier)
// Handle a variadic parameter (can only be the final parameter).
if variadic && i == t.Len()-1 {
typ = strings.Replace(typ, "[]", "...", 1)
}
fmt.Fprintf(&b, "%v %v", el.Name(), typ)
}
b.WriteByte(')')
return b.String()
}
// isParameter returns true if the given *types.Var is a parameter to the given
// *types.Signature.
func isParameter(sig *types.Signature, v *types.Var) bool {
if sig == nil {
return false
}
for i := 0; i < sig.Params().Len(); i++ {
if sig.Params().At(i) == v {
return true
}
}
return false
}
// qualifier returns a function that appropriately formats a types.PkgName
// appearing in a *ast.File.
func qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
// Construct mapping of import paths to their defined or implicit names.
imports := make(map[*types.Package]string)
for _, imp := range f.Imports {
var obj types.Object
if imp.Name != nil {
obj = info.Defs[imp.Name]
} else {
obj = info.Implicits[imp]
}
if pkgname, ok := obj.(*types.PkgName); ok {
imports[pkgname.Imported()] = pkgname.Name()
}
}
// Define qualifier to replace full package paths with names of the imports.
return func(pkg *types.Package) string {
if pkg == pkg {
return ""
}
if name, ok := imports[pkg]; ok {
return name
}
return pkg.Name()
}
}
// enclosingFunction returns the signature of the function enclosing the given
// position.
func enclosingFunction(path []ast.Node, pos token.Pos, info *types.Info) *types.Signature {
for _, node := range path {
switch t := node.(type) {
case *ast.FuncDecl:
if obj, ok := info.Defs[t.Name]; ok {
return obj.Type().(*types.Signature)
}
case *ast.FuncLit:
if typ, ok := info.Types[t]; ok {
return typ.Type.(*types.Signature)
}
}
}
return nil
}
// expectedType returns the expected type for an expression at the query position.
func expectedType(path []ast.Node, pos token.Pos, info *types.Info) types.Type {
for i, node := range path {
if i == 2 {
break
}
switch expr := node.(type) {
case *ast.BinaryExpr:
// Determine if query position comes from left or right of op.
e := expr.X
if pos < expr.OpPos {
e = expr.Y
}
if tv, ok := info.Types[e]; ok {
return tv.Type
}
case *ast.AssignStmt:
// Only rank completions if you are on the right side of the token.
if pos <= expr.TokPos {
break
}
i := exprAtPos(pos, expr.Rhs)
if i >= len(expr.Lhs) {
i = len(expr.Lhs) - 1
}
if tv, ok := info.Types[expr.Lhs[i]]; ok {
return tv.Type
}
case *ast.CallExpr:
if tv, ok := info.Types[expr.Fun]; ok {
if sig, ok := tv.Type.(*types.Signature); ok {
if sig.Params().Len() == 0 {
return nil
}
i := exprAtPos(pos, expr.Args)
// Make sure not to run past the end of expected parameters.
if i >= sig.Params().Len() {
i = sig.Params().Len() - 1
}
return sig.Params().At(i).Type()
}
}
}
}
return nil
}
// matchingTypes reports whether actual is a good candidate type
// for a completion in a context of the expected type.
func matchingTypes(expected, actual types.Type) bool {
// Use a function's return type as its type.
if sig, ok := actual.(*types.Signature); ok {
if sig.Results().Len() == 1 {
actual = sig.Results().At(0).Type()
}
}
return types.Identical(types.Default(expected), types.Default(actual))
}
// exprAtPos returns the index of the expression containing pos.
func exprAtPos(pos token.Pos, args []ast.Expr) int {
for i, expr := range args {
if expr.Pos() <= pos && pos <= expr.End() {
return i
}
}
return len(args)
}
// fieldSelections returns the set of fields that can
// be selected from a value of type T.
func fieldSelections(T types.Type) (fields []*types.Var) {
// TODO(adonovan): this algorithm doesn't exclude ambiguous
// selections that match more than one field/method.
// types.NewSelectionSet should do that for us.
seen := make(map[types.Type]bool) // for termination on recursive types
var visit func(T types.Type)
visit = func(T types.Type) {
if !seen[T] {
seen[T] = true
if T, ok := deref(T).Underlying().(*types.Struct); ok {
for i := 0; i < T.NumFields(); i++ {
f := T.Field(i)
fields = append(fields, f)
if f.Anonymous() {
visit(f.Type())
}
}
}
}
}
visit(T)
return fields
}
func isPointer(T types.Type) bool {
_, ok := T.(*types.Pointer)
return ok
}
// deref returns a pointer's element type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
if p, ok := typ.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return typ
}
// resolveInvalid traverses the node of the AST that defines the scope
// containing the declaration of obj, and attempts to find a user-friendly
// name for its invalid type. The resulting Object and its Type are fake.
func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Object {
// Construct a fake type for the object and return a fake object with this type.
formatResult := func(expr ast.Expr) types.Object {
var typename string
switch t := expr.(type) {
case *ast.SelectorExpr:
typename = fmt.Sprintf("%s.%s", t.X, t.Sel)
case *ast.Ident:
typename = t.String()
default:
return nil
}
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), nil, nil)
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
}
var resultExpr ast.Expr
ast.Inspect(node, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.ValueSpec:
for _, name := range n.Names {
if info.Defs[name] == obj {
resultExpr = n.Type
}
}
return false
case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
for _, name := range n.Names {
if info.Defs[name] == obj {
resultExpr = n.Type
}
}
return false
// TODO(rstambler): Handle range statements.
default:
return true
}
})
return formatResult(resultExpr)
}
type itemDetails struct {
label, detail string
}
var builtinDetails = map[string]itemDetails{
"append": { // append(slice []T, elems ...T)
label: "append(slice []T, elems ...T)",
detail: "[]T",
},
"cap": { // cap(v []T) int
label: "cap(v []T)",
detail: "int",
},
"close": { // close(c chan<- T)
label: "close(c chan<- T)",
},
"complex": { // complex(r, i float64) complex128
label: "complex(real, imag float64)",
detail: "complex128",
},
"copy": { // copy(dst, src []T) int
label: "copy(dst, src []T)",
detail: "int",
},
"delete": { // delete(m map[T]T1, key T)
label: "delete(m map[K]V, key K)",
},
"imag": { // imag(c complex128) float64
label: "imag(complex128)",
detail: "float64",
},
"len": { // len(v T) int
label: "len(T)",
detail: "int",
},
"make": { // make(t T, size ...int) T
label: "make(t T, size ...int)",
detail: "T",
},
"new": { // new(T) *T
label: "new(T)",
detail: "*T",
},
"panic": { // panic(v interface{})
label: "panic(interface{})",
},
"print": { // print(args ...T)
label: "print(args ...T)",
},
"println": { // println(args ...T)
label: "println(args ...T)",
},
"real": { // real(c complex128) float64
label: "real(complex128)",
detail: "float64",
},
"recover": { // recover() interface{}
label: "recover()",
detail: "interface{}",
},
}

View File

@@ -1,121 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"bytes"
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"io/ioutil"
"golang.org/x/tools/go/ast/astutil"
)
func Definition(ctx context.Context, f *File, pos token.Pos) (Range, error) {
fAST, err := f.GetAST()
if err != nil {
return Range{}, err
}
pkg, err := f.GetPackage()
if err != nil {
return Range{}, err
}
i, err := findIdentifier(fAST, pos)
if err != nil {
return Range{}, err
}
if i.ident == nil {
return Range{}, fmt.Errorf("definition was not a valid identifier")
}
obj := pkg.TypesInfo.ObjectOf(i.ident)
if obj == nil {
return Range{}, fmt.Errorf("no object")
}
if i.wasEmbeddedField {
// the original position was on the embedded field declaration
// so we try to dig out the type and jump to that instead
if v, ok := obj.(*types.Var); ok {
if n, ok := v.Type().(*types.Named); ok {
obj = n.Obj()
}
}
}
return objToRange(f.view.Config.Fset, obj), nil
}
// ident returns the ident plus any extra information needed
type ident struct {
ident *ast.Ident
wasEmbeddedField bool
}
// findIdentifier returns the ast.Ident for a position
// in a file, accounting for a potentially incomplete selector.
func findIdentifier(f *ast.File, pos token.Pos) (ident, error) {
m, err := checkIdentifier(f, pos)
if err != nil {
return ident{}, err
}
if m.ident != nil {
return m, nil
}
// If the position is not an identifier but immediately follows
// an identifier or selector period (as is common when
// requesting a completion), use the path to the preceding node.
return checkIdentifier(f, pos-1)
}
// checkIdentifier checks a single position for a potential identifier.
func checkIdentifier(f *ast.File, pos token.Pos) (ident, error) {
path, _ := astutil.PathEnclosingInterval(f, pos, pos)
result := ident{}
if path == nil {
return result, fmt.Errorf("can't find node enclosing position")
}
switch node := path[0].(type) {
case *ast.Ident:
result.ident = node
case *ast.SelectorExpr:
result.ident = node.Sel
}
if result.ident != nil {
for _, n := range path[1:] {
if field, ok := n.(*ast.Field); ok {
result.wasEmbeddedField = len(field.Names) == 0
}
}
}
return result, nil
}
func objToRange(fSet *token.FileSet, obj types.Object) Range {
p := obj.Pos()
f := fSet.File(p)
pos := f.Position(p)
if pos.Column == 1 {
// Column is 1, so we probably do not have full position information
// Currently exportdata does not store the column.
// For now we attempt to read the original source and find the identifier
// within the line. If we find it we patch the column to match its offset.
// TODO: we have probably already added the full data for the file to the
// fileset, we ought to track it rather than adding it over and over again
// TODO: if we parse from source, we will never need this hack
if src, err := ioutil.ReadFile(pos.Filename); err == nil {
newF := fSet.AddFile(pos.Filename, -1, len(src))
newF.SetLinesForContent(src)
lineStart := lineStart(newF, pos.Line)
offset := newF.Offset(lineStart)
col := bytes.Index(src[offset:], []byte(obj.Name()))
p = newF.Pos(offset + col)
}
}
return Range{
Start: p,
End: p + token.Pos(len([]byte(obj.Name()))), // TODO: use real range of obj
}
}

View File

@@ -1,148 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"go/token"
"strconv"
"strings"
"golang.org/x/tools/go/packages"
)
type Diagnostic struct {
Range Range
Severity DiagnosticSeverity
Message string
}
type DiagnosticSeverity int
const (
SeverityError DiagnosticSeverity = iota
SeverityWarning
SeverityHint
SeverityInformation
)
func Diagnostics(ctx context.Context, v *View, f *File) (map[string][]Diagnostic, error) {
pkg, err := f.GetPackage()
if err != nil {
return nil, err
}
// Prepare the reports we will send for this package.
reports := make(map[string][]Diagnostic)
for _, filename := range pkg.GoFiles {
reports[filename] = []Diagnostic{}
}
var parseErrors, typeErrors []packages.Error
for _, err := range pkg.Errors {
switch err.Kind {
case packages.ParseError:
parseErrors = append(parseErrors, err)
case packages.TypeError:
typeErrors = append(typeErrors, err)
default:
// ignore other types of errors
continue
}
}
// Don't report type errors if there are parse errors.
diags := typeErrors
if len(parseErrors) > 0 {
diags = parseErrors
}
for _, diag := range diags {
filename, start := v.errorPos(diag)
// TODO(rstambler): Add support for diagnostic ranges.
end := start
diagnostic := Diagnostic{
Range: Range{
Start: start,
End: end,
},
Message: diag.Msg,
Severity: SeverityError,
}
if _, ok := reports[filename]; ok {
reports[filename] = append(reports[filename], diagnostic)
}
}
return reports, nil
}
func (v *View) errorPos(pkgErr packages.Error) (string, token.Pos) {
remainder1, first, hasLine := chop(pkgErr.Pos)
remainder2, second, hasColumn := chop(remainder1)
var pos token.Position
if hasLine && hasColumn {
pos.Filename = remainder2
pos.Line = second
pos.Column = first
} else if hasLine {
pos.Filename = remainder1
pos.Line = first
}
f := v.GetFile(ToURI(pos.Filename))
if f == nil {
return "", token.NoPos
}
tok, err := f.GetToken()
if err != nil {
return "", token.NoPos
}
return pos.Filename, fromTokenPosition(tok, pos)
}
func chop(text string) (remainder string, value int, ok bool) {
i := strings.LastIndex(text, ":")
if i < 0 {
return text, 0, false
}
v, err := strconv.ParseInt(text[i+1:], 10, 64)
if err != nil {
return text, 0, false
}
return text[:i], int(v), true
}
// fromTokenPosition converts a token.Position (1-based line and column
// number) to a token.Pos (byte offset value).
// It requires the token file the pos belongs to in order to do this.
func fromTokenPosition(f *token.File, pos token.Position) token.Pos {
line := lineStart(f, pos.Line)
return line + token.Pos(pos.Column-1) // TODO: this is wrong, bytes not characters
}
// this functionality was borrowed from the analysisutil package
func lineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
//
// TODO(adonovan): eventually replace this function with the
// simpler and more efficient (*go/token.File).LineStart, added
// in go1.12.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}

View File

@@ -1,131 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"fmt"
"go/ast"
"go/token"
"io/ioutil"
"golang.org/x/tools/go/packages"
)
// File holds all the information we know about a file.
type File struct {
URI URI
view *View
active bool
content []byte
ast *ast.File
token *token.File
pkg *packages.Package
}
// Range represents a start and end position.
// Because Range is based purely on two token.Pos entries, it is not self
// contained. You need access to a token.FileSet to regain the file
// information.
type Range struct {
Start token.Pos
End token.Pos
}
// TextEdit represents a change to a section of a document.
// The text within the specified range should be replaced by the supplied new text.
type TextEdit struct {
Range Range
NewText string
}
// SetContent sets the overlay contents for a file.
// Setting it to nil will revert it to the on disk contents, and remove it
// from the active set.
func (f *File) SetContent(content []byte) {
f.view.mu.Lock()
defer f.view.mu.Unlock()
f.content = content
// the ast and token fields are invalid
f.ast = nil
f.token = nil
f.pkg = nil
// and we might need to update the overlay
switch {
case f.active && content == nil:
// we were active, and want to forget the content
f.active = false
if filename, err := f.URI.Filename(); err == nil {
delete(f.view.Config.Overlay, filename)
}
f.content = nil
case content != nil:
// an active overlay, update the map
f.active = true
if filename, err := f.URI.Filename(); err == nil {
f.view.Config.Overlay[filename] = f.content
}
}
}
// Read returns the contents of the file, reading it from file system if needed.
func (f *File) Read() ([]byte, error) {
f.view.mu.Lock()
defer f.view.mu.Unlock()
return f.read()
}
func (f *File) GetToken() (*token.File, error) {
f.view.mu.Lock()
defer f.view.mu.Unlock()
if f.token == nil {
if err := f.view.parse(f.URI); err != nil {
return nil, err
}
if f.token == nil {
return nil, fmt.Errorf("failed to find or parse %v", f.URI)
}
}
return f.token, nil
}
func (f *File) GetAST() (*ast.File, error) {
f.view.mu.Lock()
defer f.view.mu.Unlock()
if f.ast == nil {
if err := f.view.parse(f.URI); err != nil {
return nil, err
}
}
return f.ast, nil
}
func (f *File) GetPackage() (*packages.Package, error) {
f.view.mu.Lock()
defer f.view.mu.Unlock()
if f.pkg == nil {
if err := f.view.parse(f.URI); err != nil {
return nil, err
}
}
return f.pkg, nil
}
// read is the internal part of Read that presumes the lock is already held
func (f *File) read() ([]byte, error) {
if f.content != nil {
return f.content, nil
}
// we don't know the content yet, so read it
filename, err := f.URI.Filename()
if err != nil {
return nil, err
}
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
f.content = content
return f.content, nil
}

View File

@@ -1,59 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"bytes"
"context"
"fmt"
"go/ast"
"go/format"
"golang.org/x/tools/go/ast/astutil"
)
// Format formats a document with a given range.
func Format(ctx context.Context, f *File, rng Range) ([]TextEdit, error) {
fAST, err := f.GetAST()
if err != nil {
return nil, err
}
path, exact := astutil.PathEnclosingInterval(fAST, rng.Start, rng.End)
if !exact || len(path) == 0 {
return nil, fmt.Errorf("no exact AST node matching the specified range")
}
node := path[0]
// format.Node can fail when the AST contains a bad expression or
// statement. For now, we preemptively check for one.
// TODO(rstambler): This should really return an error from format.Node.
var isBad bool
ast.Inspect(node, func(n ast.Node) bool {
switch n.(type) {
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
isBad = true
return false
default:
return true
}
})
if isBad {
return nil, fmt.Errorf("unable to format file due to a badly formatted AST")
}
// format.Node changes slightly from one release to another, so the version
// of Go used to build the LSP server will determine how it formats code.
// This should be acceptable for all users, who likely be prompted to rebuild
// the LSP server on each Go release.
buf := &bytes.Buffer{}
if err := format.Node(buf, f.view.Config.Fset, node); err != nil {
return nil, err
}
// TODO(rstambler): Compute text edits instead of replacing whole file.
return []TextEdit{
{
Range: rng,
NewText: buf.String(),
},
}, nil
}

View File

@@ -1,118 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/ast/astutil"
)
type SignatureInformation struct {
Label string
Parameters []ParameterInformation
ActiveParameter int
}
type ParameterInformation struct {
Label string
}
func SignatureHelp(ctx context.Context, f *File, pos token.Pos) (*SignatureInformation, error) {
fAST, err := f.GetAST()
if err != nil {
return nil, err
}
pkg, err := f.GetPackage()
if err != nil {
return nil, err
}
// Find a call expression surrounding the query position.
var callExpr *ast.CallExpr
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
if path == nil {
return nil, fmt.Errorf("cannot find node enclosing position")
}
for _, node := range path {
if c, ok := node.(*ast.CallExpr); ok {
callExpr = c
break
}
}
if callExpr == nil || callExpr.Fun == nil {
return nil, fmt.Errorf("cannot find an enclosing function")
}
// Get the type information for the function corresponding to the call expression.
var obj types.Object
switch t := callExpr.Fun.(type) {
case *ast.Ident:
obj = pkg.TypesInfo.ObjectOf(t)
case *ast.SelectorExpr:
obj = pkg.TypesInfo.ObjectOf(t.Sel)
default:
return nil, fmt.Errorf("the enclosing function is malformed")
}
if obj == nil {
return nil, fmt.Errorf("cannot resolve %s", callExpr.Fun)
}
// Find the signature corresponding to the object.
var sig *types.Signature
switch obj.(type) {
case *types.Var:
if underlying, ok := obj.Type().Underlying().(*types.Signature); ok {
sig = underlying
}
case *types.Func:
sig = obj.Type().(*types.Signature)
}
if sig == nil {
return nil, fmt.Errorf("no function signatures found for %s", obj.Name())
}
pkgStringer := qualifier(fAST, pkg.Types, pkg.TypesInfo)
var paramInfo []ParameterInformation
for i := 0; i < sig.Params().Len(); i++ {
param := sig.Params().At(i)
label := types.TypeString(param.Type(), pkgStringer)
if param.Name() != "" {
label = fmt.Sprintf("%s %s", param.Name(), label)
}
paramInfo = append(paramInfo, ParameterInformation{
Label: label,
})
}
// Determine the query position relative to the number of parameters in the function.
var activeParam int
var start, end token.Pos
for i, expr := range callExpr.Args {
if start == token.NoPos {
start = expr.Pos()
}
end = expr.End()
if i < len(callExpr.Args)-1 {
end = callExpr.Args[i+1].Pos() - 1 // comma
}
if start <= pos && pos <= end {
break
}
activeParam++
start = expr.Pos() + 1 // to account for commas
}
// Label for function, qualified by package name.
label := obj.Name()
if pkg := pkgStringer(obj.Pkg()); pkg != "" {
label = pkg + "." + label
}
return &SignatureInformation{
Label: label + formatParams(sig.Params(), sig.Variadic(), pkgStringer),
Parameters: paramInfo,
ActiveParameter: activeParam,
}, nil
}

View File

@@ -1,47 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"fmt"
"net/url"
"path/filepath"
"runtime"
"strings"
)
const fileSchemePrefix = "file://"
// URI represents the full uri for a file.
type URI string
// Filename gets the file path for the URI.
// It will return an error if the uri is not valid, or if the URI was not
// a file URI
func (uri URI) Filename() (string, error) {
s := string(uri)
if !strings.HasPrefix(s, fileSchemePrefix) {
return "", fmt.Errorf("only file URI's are supported, got %v", uri)
}
s = s[len(fileSchemePrefix):]
s, err := url.PathUnescape(s)
if err != nil {
return s, err
}
s = filepath.FromSlash(s)
return s, nil
}
// ToURI returns a protocol URI for the supplied path.
// It will always have the file scheme.
func ToURI(path string) URI {
const prefix = "$GOROOT"
if strings.EqualFold(prefix, path[:len(prefix)]) {
suffix := path[len(prefix):]
//TODO: we need a better way to get the GOROOT that uses the packages api
path = runtime.GOROOT() + suffix
}
return URI(fileSchemePrefix + filepath.ToSlash(path))
}

View File

@@ -1,82 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"fmt"
"go/token"
"sync"
"golang.org/x/tools/go/packages"
)
type View struct {
mu sync.Mutex // protects all mutable state of the view
Config *packages.Config
files map[URI]*File
}
func NewView() *View {
return &View{
Config: &packages.Config{
Mode: packages.LoadSyntax,
Fset: token.NewFileSet(),
Tests: true,
Overlay: make(map[string][]byte),
},
files: make(map[URI]*File),
}
}
// GetFile returns a File for the given uri.
// It will always succeed, adding the file to the managed set if needed.
func (v *View) GetFile(uri URI) *File {
v.mu.Lock()
f := v.getFile(uri)
v.mu.Unlock()
return f
}
// getFile is the unlocked internal implementation of GetFile.
func (v *View) getFile(uri URI) *File {
f, found := v.files[uri]
if !found {
f = &File{
URI: uri,
view: v,
}
v.files[f.URI] = f
}
return f
}
func (v *View) parse(uri URI) error {
path, err := uri.Filename()
if err != nil {
return err
}
pkgs, err := packages.Load(v.Config, fmt.Sprintf("file=%s", path))
if len(pkgs) == 0 {
if err == nil {
err = fmt.Errorf("no packages found for %s", path)
}
return err
}
for _, pkg := range pkgs {
// add everything we find to the files cache
for _, fAST := range pkg.Syntax {
// if a file was in multiple packages, which token/ast/pkg do we store
fToken := v.Config.Fset.File(fAST.Pos())
fURI := ToURI(fToken.Name())
f := v.getFile(fURI)
f.token = fToken
f.ast = fAST
f.pkg = pkg
}
}
return nil
}

View File

@@ -1,18 +0,0 @@
package bad
func stuff() {
x := "heeeeyyyy"
random2(x) //@diag("x", "cannot use x (variable of type string) as int value in argument to random2")
random2(1)
y := 3 //@diag("y", "y declared but not used")
}
type bob struct {
x int
}
func _() {
_ = &bob{
f: 0, //@diag("f", "unknown field f in struct literal")
}
}

View File

@@ -1,6 +0,0 @@
package bad
func random2(y int) int {
x := 6 //@diag("x", "x declared but not used")
return y
}

View File

@@ -1,7 +0,0 @@
package bar
import "golang.org/x/tools/internal/lsp/foo"
func Bar() {
foo.Foo()
}

View File

@@ -1,7 +0,0 @@
package baz
import "golang.org/x/tools/internal/lsp/bar"
func Baz() {
bar.Bar()
}

View File

@@ -1,23 +0,0 @@
package foo
type StructFoo struct { //@item(StructFoo, "StructFoo", "struct{...}", "struct")
Value int //@item(Value, "Value", "int", "field")
}
// TODO(rstambler): Create pre-set builtins?
/* Error() */ //@item(Error, "Error()", "string", "method")
func Foo() { //@item(Foo, "Foo()", "", "func")
var err error
err.Error() //@complete("E", Error)
}
func _() {
var sFoo StructFoo //@complete("t", StructFoo)
if x := sFoo; x.Value == 1 { //@complete("V", Value)
return
}
}
//@complete("", Foo, IntFoo, StructFoo)
type IntFoo int //@item(IntFoo, "IntFoo", "int", "type")

View File

@@ -1,21 +0,0 @@
package format //@format("package")
import (
"fmt"
"runtime"
"log"
)
func hello() {
var x int //@diag("x", "x declared but not used")
}
func hi() {
runtime.GOROOT()
fmt.Printf("")
log.Printf("")
}

View File

@@ -1,9 +0,0 @@
package format //@format("package")
import (
"log"
)
func goodbye() {
log.Printf("byeeeee")
}

View File

@@ -1,11 +0,0 @@
// A comment just to push the positions out
package a
type A string //@A
func Stuff() { //@Stuff
x := 5
Random2(x) //@godef("dom2", Random2)
Random() //@godef("()", Random)
}

View File

@@ -1,23 +0,0 @@
package a
func Random() int { //@Random
y := 6 + 7
return y
}
func Random2(y int) int { //@Random2,mark(RandomParamY, "y")
return y //@godef("y", RandomParamY)
}
type Pos struct {
x, y int //@mark(PosX, "x"),mark(PosY, "y")
}
func (p *Pos) Sum() int { //@mark(PosSum, "Sum")
return p.x + p.y //@godef("x", PosX)
}
func _() {
var p Pos
_ = p.Sum() //@godef("()", PosSum)
}

View File

@@ -1,23 +0,0 @@
package b
import "golang.org/x/tools/internal/lsp/godef/a"
type S1 struct { //@S1
F1 int //@mark(S1F1, "F1")
S2 //@godef("S2", S2), mark(S1S2, "S2")
a.A //@godef("A", A)
}
type S2 struct { //@S2
F1 string //@mark(S2F1, "F1")
F2 int //@mark(S2F2, "F2")
}
func Bar() {
a.Stuff() //@godef("Stuff", Stuff)
var x S1 //@godef("S1", S1)
_ = x.S2 //@godef("S2", S1S2)
_ = x.F1 //@godef("F1", S1F1)
_ = x.F2 //@godef("F2", S2F2)
_ = x.S2.F1 //@godef("F1", S2F1)
}

View File

@@ -1,8 +0,0 @@
package b
// This is the in-editor version of the file.
// The on-disk version is in c.go.saved.
var _ = S1{ //@godef("S1", S1)
F1: 99, //@godef("F1", S1F1)
}

View File

@@ -1,7 +0,0 @@
package b
// This is the on-disk version of c.go, which represents
// the in-editor version of the file.
}

View File

@@ -1,11 +0,0 @@
package broken
import "fmt"
func unclosedIf() {
if false {
var myUnclosedIf string //@myUnclosedIf
fmt.Printf("s = %v\n", myUnclosedIf) //@godef("my", myUnclosedIf)
}
//@diag(EOF, "expected ';', found 'EOF'")
//@diag(EOF, "expected '}', found 'EOF'")

View File

@@ -1,6 +0,0 @@
package good
func stuff() {
x := 5
random2(x)
}

View File

@@ -1,10 +0,0 @@
package good
func random() int {
y := 6 + 7
return y
}
func random2(y int) int {
return y
}

View File

@@ -1,11 +0,0 @@
package noparse
func bye(x int) {
hi()
}
func stuff() {
x := 5
}
func .() {} //@diag(".", "expected 'IDENT', found '.'")

View File

@@ -1,11 +0,0 @@
// +build !go1.11
// This file does not actually test anything
// on 1.10 the errors are different
package noparse_format
func what() {
// we need a diagnostic below so we have the same count as the main file
var b int //@diag("b", "b declared but not used")
if true {}
}

View File

@@ -1,9 +0,0 @@
// +build go1.11
package noparse_format //@format("package")
func what() {
var b int
if { hi() //@diag("{", "missing condition in if statement")
}
}

View File

@@ -1,159 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build golangorg
// Package memcache provides a minimally compatible interface for
// google.golang.org/appengine/memcache
// and stores the data in Redis (e.g., via Cloud Memorystore).
package memcache
import (
"bytes"
"context"
"encoding/gob"
"encoding/json"
"errors"
"time"
"github.com/gomodule/redigo/redis"
)
var ErrCacheMiss = errors.New("memcache: cache miss")
func New(addr string) *Client {
const maxConns = 20
pool := redis.NewPool(func() (redis.Conn, error) {
return redis.Dial("tcp", addr)
}, maxConns)
return &Client{
pool: pool,
}
}
type Client struct {
pool *redis.Pool
}
type CodecClient struct {
client *Client
codec Codec
}
type Item struct {
Key string
Value []byte
Object interface{} // Used with Codec.
Expiration time.Duration // Read-only.
}
func (c *Client) WithCodec(codec Codec) *CodecClient {
return &CodecClient{
c, codec,
}
}
func (c *Client) Delete(ctx context.Context, key string) error {
conn, err := c.pool.GetContext(ctx)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Do("DEL", key)
return err
}
func (c *CodecClient) Delete(ctx context.Context, key string) error {
return c.client.Delete(ctx, key)
}
func (c *Client) Set(ctx context.Context, item *Item) error {
if item.Value == nil {
return errors.New("nil item value")
}
return c.set(ctx, item.Key, item.Value, item.Expiration)
}
func (c *CodecClient) Set(ctx context.Context, item *Item) error {
if item.Object == nil {
return errors.New("nil object value")
}
b, err := c.codec.Marshal(item.Object)
if err != nil {
return err
}
return c.client.set(ctx, item.Key, b, item.Expiration)
}
func (c *Client) set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
conn, err := c.pool.GetContext(ctx)
if err != nil {
return err
}
defer conn.Close()
if expiration == 0 {
_, err := conn.Do("SET", key, value)
return err
}
// NOTE(cbro): redis does not support expiry in units more granular than a second.
exp := int64(expiration.Seconds())
if exp == 0 {
// Redis doesn't allow a zero expiration, delete the key instead.
_, err := conn.Do("DEL", key)
return err
}
_, err = conn.Do("SETEX", key, exp, value)
return err
}
// Get gets the item.
func (c *Client) Get(ctx context.Context, key string) ([]byte, error) {
conn, err := c.pool.GetContext(ctx)
if err != nil {
return nil, err
}
defer conn.Close()
b, err := redis.Bytes(conn.Do("GET", key))
if err == redis.ErrNil {
err = ErrCacheMiss
}
return b, err
}
func (c *CodecClient) Get(ctx context.Context, key string, v interface{}) error {
b, err := c.client.Get(ctx, key)
if err != nil {
return err
}
return c.codec.Unmarshal(b, v)
}
var (
Gob = Codec{gobMarshal, gobUnmarshal}
JSON = Codec{json.Marshal, json.Unmarshal}
)
type Codec struct {
Marshal func(interface{}) ([]byte, error)
Unmarshal func([]byte, interface{}) error
}
func gobMarshal(v interface{}) ([]byte, error) {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func gobUnmarshal(data []byte, v interface{}) error {
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
}

View File

@@ -1,85 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build golangorg
package memcache
import (
"context"
"os"
"testing"
"time"
)
func getClient(t *testing.T) *Client {
t.Helper()
addr := os.Getenv("GOLANG_REDIS_ADDR")
if addr == "" {
t.Skip("skipping because GOLANG_REDIS_ADDR is unset")
}
return New(addr)
}
func TestCacheMiss(t *testing.T) {
c := getClient(t)
ctx := context.Background()
if _, err := c.Get(ctx, "doesnotexist"); err != ErrCacheMiss {
t.Errorf("got %v; want ErrCacheMiss", err)
}
}
func TestExpiry(t *testing.T) {
c := getClient(t).WithCodec(Gob)
ctx := context.Background()
key := "testexpiry"
firstTime := time.Now()
err := c.Set(ctx, &Item{
Key: key,
Object: firstTime,
Expiration: 3500 * time.Millisecond, // NOTE: check that non-rounded expiries work.
})
if err != nil {
t.Fatalf("Set: %v", err)
}
var newTime time.Time
if err := c.Get(ctx, key, &newTime); err != nil {
t.Fatalf("Get: %v", err)
}
if !firstTime.Equal(newTime) {
t.Errorf("Get: got value %v, want %v", newTime, firstTime)
}
time.Sleep(4 * time.Second)
if err := c.Get(ctx, key, &newTime); err != ErrCacheMiss {
t.Errorf("Get: got %v, want ErrCacheMiss", err)
}
}
func TestShortExpiry(t *testing.T) {
c := getClient(t).WithCodec(Gob)
ctx := context.Background()
key := "testshortexpiry"
err := c.Set(ctx, &Item{
Key: key,
Value: []byte("ok"),
Expiration: time.Millisecond,
})
if err != nil {
t.Fatalf("Set: %v", err)
}
if err := c.Get(ctx, key, nil); err != ErrCacheMiss {
t.Errorf("GetBytes: got %v, want ErrCacheMiss", err)
}
}

View File

@@ -1,388 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package semver implements comparison of semantic version strings.
// In this package, semantic version strings must begin with a leading "v",
// as in "v1.0.0".
//
// The general form of a semantic version string accepted by this package is
//
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
//
// where square brackets indicate optional parts of the syntax;
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
// using only alphanumeric characters and hyphens; and
// all-numeric PRERELEASE identifiers must not have leading zeros.
//
// This package follows Semantic Versioning 2.0.0 (see semver.org)
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
package semver
// parsed returns the parsed form of a semantic version string.
type parsed struct {
major string
minor string
patch string
short string
prerelease string
build string
err string
}
// IsValid reports whether v is a valid semantic version string.
func IsValid(v string) bool {
_, ok := parse(v)
return ok
}
// Canonical returns the canonical formatting of the semantic version v.
// It fills in any missing .MINOR or .PATCH and discards build metadata.
// Two semantic versions compare equal only if their canonical formattings
// are identical strings.
// The canonical invalid semantic version is the empty string.
func Canonical(v string) string {
p, ok := parse(v)
if !ok {
return ""
}
if p.build != "" {
return v[:len(v)-len(p.build)]
}
if p.short != "" {
return v + p.short
}
return v
}
// Major returns the major version prefix of the semantic version v.
// For example, Major("v2.1.0") == "v2".
// If v is an invalid semantic version string, Major returns the empty string.
func Major(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return v[:1+len(pv.major)]
}
// MajorMinor returns the major.minor version prefix of the semantic version v.
// For example, MajorMinor("v2.1.0") == "v2.1".
// If v is an invalid semantic version string, MajorMinor returns the empty string.
func MajorMinor(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
i := 1 + len(pv.major)
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
return v[:j]
}
return v[:i] + "." + pv.minor
}
// Prerelease returns the prerelease suffix of the semantic version v.
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
// If v is an invalid semantic version string, Prerelease returns the empty string.
func Prerelease(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.prerelease
}
// Build returns the build suffix of the semantic version v.
// For example, Build("v2.1.0+meta") == "+meta".
// If v is an invalid semantic version string, Build returns the empty string.
func Build(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.build
}
// Compare returns an integer comparing two versions according to
// according to semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
//
// An invalid semantic version string is considered less than a valid one.
// All invalid semantic version strings compare equal to each other.
func Compare(v, w string) int {
pv, ok1 := parse(v)
pw, ok2 := parse(w)
if !ok1 && !ok2 {
return 0
}
if !ok1 {
return -1
}
if !ok2 {
return +1
}
if c := compareInt(pv.major, pw.major); c != 0 {
return c
}
if c := compareInt(pv.minor, pw.minor); c != 0 {
return c
}
if c := compareInt(pv.patch, pw.patch); c != 0 {
return c
}
return comparePrerelease(pv.prerelease, pw.prerelease)
}
// Max canonicalizes its arguments and then returns the version string
// that compares greater.
func Max(v, w string) string {
v = Canonical(v)
w = Canonical(w)
if Compare(v, w) > 0 {
return v
}
return w
}
func parse(v string) (p parsed, ok bool) {
if v == "" || v[0] != 'v' {
p.err = "missing v prefix"
return
}
p.major, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad major version"
return
}
if v == "" {
p.minor = "0"
p.patch = "0"
p.short = ".0.0"
return
}
if v[0] != '.' {
p.err = "bad minor prefix"
ok = false
return
}
p.minor, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad minor version"
return
}
if v == "" {
p.patch = "0"
p.short = ".0"
return
}
if v[0] != '.' {
p.err = "bad patch prefix"
ok = false
return
}
p.patch, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad patch version"
return
}
if len(v) > 0 && v[0] == '-' {
p.prerelease, v, ok = parsePrerelease(v)
if !ok {
p.err = "bad prerelease"
return
}
}
if len(v) > 0 && v[0] == '+' {
p.build, v, ok = parseBuild(v)
if !ok {
p.err = "bad build"
return
}
}
if v != "" {
p.err = "junk on end"
ok = false
return
}
ok = true
return
}
func parseInt(v string) (t, rest string, ok bool) {
if v == "" {
return
}
if v[0] < '0' || '9' < v[0] {
return
}
i := 1
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
if v[0] == '0' && i != 1 {
return
}
return v[:i], v[i:], true
}
func parsePrerelease(v string) (t, rest string, ok bool) {
// "A pre-release version MAY be denoted by appending a hyphen and
// a series of dot separated identifiers immediately following the patch version.
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
if v == "" || v[0] != '-' {
return
}
i := 1
start := 1
for i < len(v) && v[i] != '+' {
if !isIdentChar(v[i]) && v[i] != '.' {
return
}
if v[i] == '.' {
if start == i || isBadNum(v[start:i]) {
return
}
start = i + 1
}
i++
}
if start == i || isBadNum(v[start:i]) {
return
}
return v[:i], v[i:], true
}
func parseBuild(v string) (t, rest string, ok bool) {
if v == "" || v[0] != '+' {
return
}
i := 1
start := 1
for i < len(v) {
if !isIdentChar(v[i]) {
return
}
if v[i] == '.' {
if start == i {
return
}
start = i + 1
}
i++
}
if start == i {
return
}
return v[:i], v[i:], true
}
func isIdentChar(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
}
func isBadNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v) && i > 1 && v[0] == '0'
}
func isNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v)
}
func compareInt(x, y string) int {
if x == y {
return 0
}
if len(x) < len(y) {
return -1
}
if len(x) > len(y) {
return +1
}
if x < y {
return -1
} else {
return +1
}
}
func comparePrerelease(x, y string) int {
// "When major, minor, and patch are equal, a pre-release version has
// lower precedence than a normal version.
// Example: 1.0.0-alpha < 1.0.0.
// Precedence for two pre-release versions with the same major, minor,
// and patch version MUST be determined by comparing each dot separated
// identifier from left to right until a difference is found as follows:
// identifiers consisting of only digits are compared numerically and
// identifiers with letters or hyphens are compared lexically in ASCII
// sort order. Numeric identifiers always have lower precedence than
// non-numeric identifiers. A larger set of pre-release fields has a
// higher precedence than a smaller set, if all of the preceding
// identifiers are equal.
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
if x == y {
return 0
}
if x == "" {
return +1
}
if y == "" {
return -1
}
for x != "" && y != "" {
x = x[1:] // skip - or .
y = y[1:] // skip - or .
var dx, dy string
dx, x = nextIdent(x)
dy, y = nextIdent(y)
if dx != dy {
ix := isNum(dx)
iy := isNum(dy)
if ix != iy {
if ix {
return -1
} else {
return +1
}
}
if ix {
if len(dx) < len(dy) {
return -1
}
if len(dx) > len(dy) {
return +1
}
}
if dx < dy {
return -1
} else {
return +1
}
}
}
if x == "" {
return -1
} else {
return +1
}
}
func nextIdent(x string) (dx, rest string) {
i := 0
for i < len(x) && x[i] != '.' {
i++
}
return x[:i], x[i:]
}

View File

@@ -1,182 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package semver
import (
"strings"
"testing"
)
var tests = []struct {
in string
out string
}{
{"bad", ""},
{"v1-alpha.beta.gamma", ""},
{"v1-pre", ""},
{"v1+meta", ""},
{"v1-pre+meta", ""},
{"v1.2-pre", ""},
{"v1.2+meta", ""},
{"v1.2-pre+meta", ""},
{"v1.0.0-alpha", "v1.0.0-alpha"},
{"v1.0.0-alpha.1", "v1.0.0-alpha.1"},
{"v1.0.0-alpha.beta", "v1.0.0-alpha.beta"},
{"v1.0.0-beta", "v1.0.0-beta"},
{"v1.0.0-beta.2", "v1.0.0-beta.2"},
{"v1.0.0-beta.11", "v1.0.0-beta.11"},
{"v1.0.0-rc.1", "v1.0.0-rc.1"},
{"v1", "v1.0.0"},
{"v1.0", "v1.0.0"},
{"v1.0.0", "v1.0.0"},
{"v1.2", "v1.2.0"},
{"v1.2.0", "v1.2.0"},
{"v1.2.3-456", "v1.2.3-456"},
{"v1.2.3-456.789", "v1.2.3-456.789"},
{"v1.2.3-456-789", "v1.2.3-456-789"},
{"v1.2.3-456a", "v1.2.3-456a"},
{"v1.2.3-pre", "v1.2.3-pre"},
{"v1.2.3-pre+meta", "v1.2.3-pre"},
{"v1.2.3-pre.1", "v1.2.3-pre.1"},
{"v1.2.3-zzz", "v1.2.3-zzz"},
{"v1.2.3", "v1.2.3"},
{"v1.2.3+meta", "v1.2.3"},
{"v1.2.3+meta-pre", "v1.2.3"},
}
func TestIsValid(t *testing.T) {
for _, tt := range tests {
ok := IsValid(tt.in)
if ok != (tt.out != "") {
t.Errorf("IsValid(%q) = %v, want %v", tt.in, ok, !ok)
}
}
}
func TestCanonical(t *testing.T) {
for _, tt := range tests {
out := Canonical(tt.in)
if out != tt.out {
t.Errorf("Canonical(%q) = %q, want %q", tt.in, out, tt.out)
}
}
}
func TestMajor(t *testing.T) {
for _, tt := range tests {
out := Major(tt.in)
want := ""
if i := strings.Index(tt.out, "."); i >= 0 {
want = tt.out[:i]
}
if out != want {
t.Errorf("Major(%q) = %q, want %q", tt.in, out, want)
}
}
}
func TestMajorMinor(t *testing.T) {
for _, tt := range tests {
out := MajorMinor(tt.in)
var want string
if tt.out != "" {
want = tt.in
if i := strings.Index(want, "+"); i >= 0 {
want = want[:i]
}
if i := strings.Index(want, "-"); i >= 0 {
want = want[:i]
}
switch strings.Count(want, ".") {
case 0:
want += ".0"
case 1:
// ok
case 2:
want = want[:strings.LastIndex(want, ".")]
}
}
if out != want {
t.Errorf("MajorMinor(%q) = %q, want %q", tt.in, out, want)
}
}
}
func TestPrerelease(t *testing.T) {
for _, tt := range tests {
pre := Prerelease(tt.in)
var want string
if tt.out != "" {
if i := strings.Index(tt.out, "-"); i >= 0 {
want = tt.out[i:]
}
}
if pre != want {
t.Errorf("Prerelease(%q) = %q, want %q", tt.in, pre, want)
}
}
}
func TestBuild(t *testing.T) {
for _, tt := range tests {
build := Build(tt.in)
var want string
if tt.out != "" {
if i := strings.Index(tt.in, "+"); i >= 0 {
want = tt.in[i:]
}
}
if build != want {
t.Errorf("Build(%q) = %q, want %q", tt.in, build, want)
}
}
}
func TestCompare(t *testing.T) {
for i, ti := range tests {
for j, tj := range tests {
cmp := Compare(ti.in, tj.in)
var want int
if ti.out == tj.out {
want = 0
} else if i < j {
want = -1
} else {
want = +1
}
if cmp != want {
t.Errorf("Compare(%q, %q) = %d, want %d", ti.in, tj.in, cmp, want)
}
}
}
}
func TestMax(t *testing.T) {
for i, ti := range tests {
for j, tj := range tests {
max := Max(ti.in, tj.in)
want := Canonical(ti.in)
if i < j {
want = Canonical(tj.in)
}
if max != want {
t.Errorf("Max(%q, %q) = %q, want %q", ti.in, tj.in, max, want)
}
}
}
}
var (
v1 = "v1.0.0+metadata-dash"
v2 = "v1.0.0+metadata-dash1"
)
func BenchmarkCompare(b *testing.B) {
for i := 0; i < b.N; i++ {
if Compare(v1, v2) != 0 {
b.Fatalf("bad compare")
}
}
}