mirror of
https://github.com/golang/go
synced 2024-11-18 19:14:40 -07:00
internal/lsp/cmd: add the definition mode
Change-Id: Ib171016fb1bb063a6424677458b554a08144465c Reviewed-on: https://go-review.googlesource.com/c/159438 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
8dbcc66f33
commit
067a2f313b
@ -11,39 +11,45 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
// Application is the main application as passed to tool.Main
|
||||
// It handles the main command line parsing and dispatch to the sub commands.
|
||||
type Application struct {
|
||||
// Core application flags
|
||||
|
||||
// Embed the basic profiling flags supported by the tool package
|
||||
tool.Profile
|
||||
|
||||
// we also include the server directly for now, so the flags work even without
|
||||
// the verb. We should remove this when we stop allowing the server verb by
|
||||
// default
|
||||
// We include the server directly for now, so the flags work even without the verb.
|
||||
// TODO: Remove this when we stop allowing the server verb by default.
|
||||
Server Server
|
||||
|
||||
// An initial, common go/packages configuration
|
||||
Config packages.Config
|
||||
}
|
||||
|
||||
// Name implements tool.Application returning the binary name.
|
||||
func (app *Application) Name() string { return "gopls" }
|
||||
|
||||
// Usage implements tool.Application returning empty extra argument usage.
|
||||
func (app *Application) Usage() string { return "<mode> [mode-flags] [mode-args]" }
|
||||
func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" }
|
||||
|
||||
// ShortHelp implements tool.Application returning the main binary help.
|
||||
func (app *Application) ShortHelp() string {
|
||||
return "The Go Language Smartness Provider."
|
||||
return "The Go Language source tools."
|
||||
}
|
||||
|
||||
// DetailedHelp implements tool.Application returning the main binary help.
|
||||
// This includes the short help for all the sub commands.
|
||||
func (app *Application) DetailedHelp(f *flag.FlagSet) {
|
||||
fmt.Fprint(f.Output(), `
|
||||
Available modes are:
|
||||
Available commands are:
|
||||
`)
|
||||
for _, c := range app.modes() {
|
||||
for _, c := range app.commands() {
|
||||
fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
|
||||
}
|
||||
fmt.Fprint(f.Output(), `
|
||||
@ -61,21 +67,27 @@ func (app *Application) Run(ctx context.Context, args ...string) error {
|
||||
tool.Main(ctx, &app.Server, args)
|
||||
return nil
|
||||
}
|
||||
mode, args := args[0], args[1:]
|
||||
for _, m := range app.modes() {
|
||||
if m.Name() == mode {
|
||||
tool.Main(ctx, m, args)
|
||||
app.Config.Mode = packages.LoadSyntax
|
||||
app.Config.Tests = true
|
||||
if app.Config.Fset == nil {
|
||||
app.Config.Fset = token.NewFileSet()
|
||||
}
|
||||
command, args := args[0], args[1:]
|
||||
for _, c := range app.commands() {
|
||||
if c.Name() == command {
|
||||
tool.Main(ctx, c, args)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return tool.CommandLineErrorf("Unknown mode %v", mode)
|
||||
return tool.CommandLineErrorf("Unknown command %v", command)
|
||||
}
|
||||
|
||||
// modes returns the set of command modes supported by the gopls tool on the
|
||||
// commands returns the set of commands supported by the gopls tool on the
|
||||
// command line.
|
||||
// The mode is specified by the first non flag argument.
|
||||
func (app *Application) modes() []tool.Application {
|
||||
// The command is specified by the first non flag argument.
|
||||
func (app *Application) commands() []tool.Application {
|
||||
return []tool.Application{
|
||||
&app.Server,
|
||||
&query{app: app},
|
||||
}
|
||||
}
|
||||
|
179
internal/lsp/cmd/definition.go
Normal file
179
internal/lsp/cmd/definition.go
Normal file
@ -0,0 +1,179 @@
|
||||
// Copyright 2019 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 cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"os"
|
||||
|
||||
guru "golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/internal/lsp/cache"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
// A Definition is the result of a 'definition' query.
|
||||
type Definition struct {
|
||||
Location Location `json:"location"` // location of the definition
|
||||
Description string `json:"description"` // description of the denoted object
|
||||
}
|
||||
|
||||
// This constant is printed in the help, and then used in a test to verify the
|
||||
// help is still valid.
|
||||
// It should be the byte offset in this file of the "Set" in "flag.FlagSet" from
|
||||
// the DetailedHelp method below.
|
||||
const exampleOffset = 1277
|
||||
|
||||
// definition implements the definition noun for the query command.
|
||||
type definition struct {
|
||||
query *query
|
||||
}
|
||||
|
||||
func (d *definition) Name() string { return "definition" }
|
||||
func (d *definition) Usage() string { return "<position>" }
|
||||
func (d *definition) ShortHelp() string { return "show declaration of selected identifier" }
|
||||
func (d *definition) DetailedHelp(f *flag.FlagSet) {
|
||||
fmt.Fprintf(f.Output(), `
|
||||
Example: show the definition of the identifier at syntax at offset %[1]v in this file (flag.FlagSet):
|
||||
|
||||
$ gopls definition internal/lsp/cmd/definition.go:#%[1]v
|
||||
|
||||
gopls definition flags are:
|
||||
`, exampleOffset)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
// Run performs the definition query as specified by args and prints the
|
||||
// results to stdout.
|
||||
func (d *definition) Run(ctx context.Context, args ...string) error {
|
||||
if len(args) != 1 {
|
||||
return tool.CommandLineErrorf("definition expects 1 argument")
|
||||
}
|
||||
view := cache.NewView(&d.query.app.Config)
|
||||
from, err := parseLocation(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := view.GetFile(ctx, source.ToURI(from.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tok, err := f.GetToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pos := tok.Pos(from.Start.Offset)
|
||||
ident, err := source.Identifier(ctx, view, f, pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ident == nil {
|
||||
return fmt.Errorf("not an identifier")
|
||||
}
|
||||
var result interface{}
|
||||
switch d.query.Emulate {
|
||||
case "":
|
||||
result, err = buildDefinition(view, ident)
|
||||
case emulateGuru:
|
||||
result, err = buildGuruDefinition(view, ident)
|
||||
default:
|
||||
return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.query.JSON {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", "\t")
|
||||
return enc.Encode(result)
|
||||
}
|
||||
switch d := result.(type) {
|
||||
case *Definition:
|
||||
fmt.Printf("%v: defined here as %s", d.Location, d.Description)
|
||||
case *guru.Definition:
|
||||
fmt.Printf("%s: defined here as %s", d.ObjPos, d.Desc)
|
||||
default:
|
||||
return fmt.Errorf("no printer for type %T", result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definition, error) {
|
||||
content, err := ident.Hover(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Definition{
|
||||
Location: newLocation(view.FileSet(), ident.Declaration.Range),
|
||||
Description: content,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildGuruDefinition(view source.View, ident *source.IdentifierInfo) (*guru.Definition, error) {
|
||||
loc := newLocation(view.FileSet(), ident.Declaration.Range)
|
||||
pkg, err := ident.File.GetPackage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// guru does not support ranges
|
||||
loc.End = loc.Start
|
||||
// Behavior that attempts to match the expected output for guru. For an example
|
||||
// of the format, see the associated definition tests.
|
||||
buf := &bytes.Buffer{}
|
||||
q := types.RelativeTo(pkg.Types)
|
||||
qualifyName := ident.Declaration.Object.Pkg() != pkg.Types
|
||||
name := ident.Name
|
||||
var suffix interface{}
|
||||
switch obj := ident.Declaration.Object.(type) {
|
||||
case *types.TypeName:
|
||||
fmt.Fprint(buf, "type")
|
||||
case *types.Var:
|
||||
if obj.IsField() {
|
||||
qualifyName = false
|
||||
fmt.Fprint(buf, "field")
|
||||
suffix = obj.Type()
|
||||
} else {
|
||||
fmt.Fprint(buf, "var")
|
||||
}
|
||||
case *types.Func:
|
||||
fmt.Fprint(buf, "func")
|
||||
typ := obj.Type()
|
||||
if obj.Type() != nil {
|
||||
if sig, ok := typ.(*types.Signature); ok {
|
||||
buf := &bytes.Buffer{}
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
if named, ok := recv.Type().(*types.Named); ok {
|
||||
fmt.Fprintf(buf, "(%s).%s", named.Obj().Name(), name)
|
||||
}
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
buf.WriteString(name)
|
||||
}
|
||||
types.WriteSignature(buf, sig, q)
|
||||
name = buf.String()
|
||||
}
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(buf, "unknown [%T]", obj)
|
||||
}
|
||||
fmt.Fprint(buf, " ")
|
||||
if qualifyName {
|
||||
fmt.Fprintf(buf, "%s.", ident.Declaration.Object.Pkg().Path())
|
||||
}
|
||||
fmt.Fprint(buf, name)
|
||||
if suffix != nil {
|
||||
fmt.Fprint(buf, " ")
|
||||
fmt.Fprint(buf, suffix)
|
||||
}
|
||||
return &guru.Definition{
|
||||
ObjPos: fmt.Sprint(loc),
|
||||
Desc: buf.String(),
|
||||
}, nil
|
||||
}
|
163
internal/lsp/cmd/definition_test.go
Normal file
163
internal/lsp/cmd/definition_test.go
Normal file
@ -0,0 +1,163 @@
|
||||
// Copyright 2019 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 cmd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
"golang.org/x/tools/internal/lsp/cmd"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
var verifyGuru = flag.Bool("verify-guru", false, "Check that the guru compatability matches")
|
||||
|
||||
func TestDefinitionHelpExample(t *testing.T) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("could not get wd: %v", err)
|
||||
return
|
||||
}
|
||||
thisFile := filepath.Join(dir, "definition.go")
|
||||
args := []string{"query", "definition", fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)}
|
||||
expect := regexp.MustCompile(`^[\w/\\:_]+flag[/\\]flag.go:\d+:\d+,\d+:\d+: defined here as type flag.FlagSet struct{.*}$`)
|
||||
got := captureStdOut(t, func() {
|
||||
tool.Main(context.Background(), &cmd.Application{}, args)
|
||||
})
|
||||
if !expect.MatchString(got) {
|
||||
t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefinition(t *testing.T) {
|
||||
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
|
||||
Name: "golang.org/fake",
|
||||
Files: packagestest.MustCopyFileTree("testdata"),
|
||||
}})
|
||||
defer exported.Cleanup()
|
||||
count := 0
|
||||
if err := exported.Expect(map[string]interface{}{
|
||||
"definition": func(fset *token.FileSet, src token.Pos, flags string, def packagestest.Range, match string) {
|
||||
count++
|
||||
args := []string{"query"}
|
||||
if flags != "" {
|
||||
args = append(args, strings.Split(flags, " ")...)
|
||||
}
|
||||
args = append(args, "definition")
|
||||
f := fset.File(src)
|
||||
loc := cmd.Location{
|
||||
Filename: f.Name(),
|
||||
Start: cmd.Position{
|
||||
Offset: f.Offset(src),
|
||||
},
|
||||
}
|
||||
loc.End = loc.Start
|
||||
args = append(args, fmt.Sprint(loc))
|
||||
app := &cmd.Application{}
|
||||
app.Config = *exported.Config
|
||||
got := captureStdOut(t, func() {
|
||||
tool.Main(context.Background(), app, args)
|
||||
})
|
||||
start := fset.Position(def.Start)
|
||||
end := fset.Position(def.End)
|
||||
expect := os.Expand(match, func(name string) string {
|
||||
switch name {
|
||||
case "file":
|
||||
return start.Filename
|
||||
case "efile":
|
||||
qfile := strconv.Quote(start.Filename)
|
||||
return qfile[1 : len(qfile)-1]
|
||||
case "line":
|
||||
return fmt.Sprint(start.Line)
|
||||
case "col":
|
||||
return fmt.Sprint(start.Column)
|
||||
case "offset":
|
||||
return fmt.Sprint(start.Offset)
|
||||
case "eline":
|
||||
return fmt.Sprint(end.Line)
|
||||
case "ecol":
|
||||
return fmt.Sprint(end.Column)
|
||||
case "eoffset":
|
||||
return fmt.Sprint(end.Offset)
|
||||
default:
|
||||
return name
|
||||
}
|
||||
})
|
||||
if *verifyGuru {
|
||||
var guruArgs []string
|
||||
runGuru := false
|
||||
for _, arg := range args {
|
||||
switch {
|
||||
case arg == "query":
|
||||
// just ignore this one
|
||||
case arg == "-json":
|
||||
guruArgs = append(guruArgs, arg)
|
||||
case arg == "-emulate=guru":
|
||||
// if we don't see this one we should not run guru
|
||||
runGuru = true
|
||||
case strings.HasPrefix(arg, "-"):
|
||||
// unknown flag, ignore it
|
||||
break
|
||||
default:
|
||||
guruArgs = append(guruArgs, arg)
|
||||
}
|
||||
}
|
||||
if runGuru {
|
||||
cmd := exec.Command("guru", guruArgs...)
|
||||
cmd.Env = exported.Config.Env
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("Could not run guru %v: %v\n%s", guruArgs, err, out)
|
||||
} else {
|
||||
guru := strings.TrimSpace(string(out))
|
||||
if !strings.HasPrefix(expect, guru) {
|
||||
t.Errorf("definition %v\nexpected:\n%s\nguru gave:\n%s", args, expect, guru)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if expect != got {
|
||||
t.Errorf("definition %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
|
||||
}
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count == 0 {
|
||||
t.Fatalf("No tests were run")
|
||||
}
|
||||
}
|
||||
|
||||
func captureStdOut(t testing.TB, f func()) string {
|
||||
r, out, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := os.Stdout
|
||||
defer func() {
|
||||
os.Stdout = old
|
||||
out.Close()
|
||||
r.Close()
|
||||
}()
|
||||
os.Stdout = out
|
||||
f()
|
||||
out.Close()
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
7
internal/lsp/cmd/export_test.go
Normal file
7
internal/lsp/cmd/export_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2019 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 cmd
|
||||
|
||||
const ExampleOffset = exampleOffset
|
161
internal/lsp/cmd/location.go
Normal file
161
internal/lsp/cmd/location.go
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2019 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
Filename string `json:"file"`
|
||||
Start Position `json:"start"`
|
||||
End Position `json:"end"`
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
Offset int `json:"offset"`
|
||||
}
|
||||
|
||||
func newLocation(fset *token.FileSet, r source.Range) Location {
|
||||
start := fset.Position(r.Start)
|
||||
end := fset.Position(r.End)
|
||||
// it should not be possible the following line to fail
|
||||
filename, _ := source.ToURI(start.Filename).Filename()
|
||||
return Location{
|
||||
Filename: filename,
|
||||
Start: Position{
|
||||
Line: start.Line,
|
||||
Column: start.Column,
|
||||
Offset: fset.File(r.Start).Offset(r.Start),
|
||||
},
|
||||
End: Position{
|
||||
Line: end.Line,
|
||||
Column: end.Column,
|
||||
Offset: fset.File(r.End).Offset(r.End),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var posRe = regexp.MustCompile(
|
||||
`(?P<file>.*):(?P<start>(?P<sline>\d+):(?P<scol>\d)+|#(?P<soff>\d+))(?P<end>:(?P<eline>\d+):(?P<ecol>\d+)|#(?P<eoff>\d+))?$`)
|
||||
|
||||
const (
|
||||
posReAll = iota
|
||||
posReFile
|
||||
posReStart
|
||||
posReSLine
|
||||
posReSCol
|
||||
posReSOff
|
||||
posReEnd
|
||||
posReELine
|
||||
posReECol
|
||||
posReEOff
|
||||
)
|
||||
|
||||
func init() {
|
||||
names := posRe.SubexpNames()
|
||||
// verify all our submatch offsets are correct
|
||||
for name, index := range map[string]int{
|
||||
"file": posReFile,
|
||||
"start": posReStart,
|
||||
"sline": posReSLine,
|
||||
"scol": posReSCol,
|
||||
"soff": posReSOff,
|
||||
"end": posReEnd,
|
||||
"eline": posReELine,
|
||||
"ecol": posReECol,
|
||||
"eoff": posReEOff,
|
||||
} {
|
||||
if names[index] == name {
|
||||
continue
|
||||
}
|
||||
// try to find it
|
||||
for test := range names {
|
||||
if names[test] == name {
|
||||
panic(fmt.Errorf("Index for %s incorrect, wanted %v have %v", name, index, test))
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("Subexp %s does not exist", name))
|
||||
}
|
||||
}
|
||||
|
||||
// parseLocation parses a string of the form "file:pos" or
|
||||
// file:start,end" where pos, start, end match either a byte offset in the
|
||||
// form #%d or a line and column in the form %d,%d.
|
||||
func parseLocation(value string) (Location, error) {
|
||||
var loc Location
|
||||
m := posRe.FindStringSubmatch(value)
|
||||
if m == nil {
|
||||
return loc, fmt.Errorf("bad location syntax %q", value)
|
||||
}
|
||||
loc.Filename = m[posReFile]
|
||||
if m[posReSLine] != "" {
|
||||
if v, err := strconv.ParseInt(m[posReSLine], 10, 32); err != nil {
|
||||
return loc, err
|
||||
} else {
|
||||
loc.Start.Line = int(v)
|
||||
}
|
||||
if v, err := strconv.ParseInt(m[posReSCol], 10, 32); err != nil {
|
||||
return loc, err
|
||||
} else {
|
||||
loc.Start.Column = int(v)
|
||||
}
|
||||
} else {
|
||||
if v, err := strconv.ParseInt(m[posReSOff], 10, 32); err != nil {
|
||||
return loc, err
|
||||
} else {
|
||||
loc.Start.Offset = int(v)
|
||||
}
|
||||
}
|
||||
if m[posReEnd] == "" {
|
||||
loc.End = loc.Start
|
||||
} else {
|
||||
if m[posReELine] != "" {
|
||||
if v, err := strconv.ParseInt(m[posReELine], 10, 32); err != nil {
|
||||
return loc, err
|
||||
} else {
|
||||
loc.End.Line = int(v)
|
||||
}
|
||||
if v, err := strconv.ParseInt(m[posReECol], 10, 32); err != nil {
|
||||
return loc, err
|
||||
} else {
|
||||
loc.End.Column = int(v)
|
||||
}
|
||||
} else {
|
||||
if v, err := strconv.ParseInt(m[posReEOff], 10, 32); err != nil {
|
||||
return loc, err
|
||||
} else {
|
||||
loc.End.Offset = int(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
func (l Location) Format(f fmt.State, c rune) {
|
||||
// we should always have a filename
|
||||
fmt.Fprint(f, l.Filename)
|
||||
// are we in line:column format or #offset format
|
||||
fmt.Fprintf(f, ":%v", l.Start)
|
||||
if l.End != l.Start {
|
||||
fmt.Fprintf(f, ",%v", l.End)
|
||||
}
|
||||
}
|
||||
|
||||
func (p Position) Format(f fmt.State, c rune) {
|
||||
// are we in line:column format or #offset format
|
||||
if p.Line > 0 {
|
||||
fmt.Fprintf(f, "%d:%d", p.Line, p.Column)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(f, "#%d", p.Offset)
|
||||
}
|
71
internal/lsp/cmd/query.go
Normal file
71
internal/lsp/cmd/query.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2019 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
// The set of possible options that can be passed through the -emulate flag,
|
||||
// which causes query to adjust its output to match that of the binary being
|
||||
// emulated.
|
||||
|
||||
// emulateGuru tells query to emulate the output format of the guru tool.
|
||||
emulateGuru = "guru"
|
||||
)
|
||||
|
||||
// query implements the query command.
|
||||
type query struct {
|
||||
JSON bool `flag:"json" help:"emit output in JSON format"`
|
||||
Emulate string `flag:"emulate" help:"compatability mode, causes gopls to emulate another tool.\nvalues depend on the operation being performed"`
|
||||
|
||||
app *Application
|
||||
}
|
||||
|
||||
func (q *query) Name() string { return "query" }
|
||||
func (q *query) Usage() string { return "query [flags] <mode> <mode args>" }
|
||||
func (q *query) ShortHelp() string {
|
||||
return "answer queries about go source code"
|
||||
}
|
||||
func (q *query) DetailedHelp(f *flag.FlagSet) {
|
||||
fmt.Fprint(f.Output(), `
|
||||
The mode argument determines the query to perform:
|
||||
`)
|
||||
for _, m := range q.modes() {
|
||||
fmt.Fprintf(f.Output(), " %s : %v\n", m.Name(), m.ShortHelp())
|
||||
}
|
||||
fmt.Fprint(f.Output(), `
|
||||
query flags are:
|
||||
`)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
// Run takes the args after command flag processing, and invokes the correct
|
||||
// query mode as specified by the first argument.
|
||||
func (q *query) Run(ctx context.Context, args ...string) error {
|
||||
if len(args) == 0 {
|
||||
return tool.CommandLineErrorf("query must be supplied a mode")
|
||||
}
|
||||
mode, args := args[0], args[1:]
|
||||
for _, m := range q.modes() {
|
||||
if m.Name() == mode {
|
||||
tool.Main(ctx, m, args)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return tool.CommandLineErrorf("unknown command %v", mode)
|
||||
}
|
||||
|
||||
// modes returns the set of modes supported by the query command.
|
||||
func (q *query) modes() []tool.Application {
|
||||
return []tool.Application{
|
||||
&definition{query: q},
|
||||
}
|
||||
}
|
@ -38,7 +38,10 @@ func (s *Server) DetailedHelp(f *flag.FlagSet) {
|
||||
fmt.Fprint(f.Output(), `
|
||||
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
|
||||
a child of an editor process.
|
||||
|
||||
gopls server flags are:
|
||||
`)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
// Run configures a server based on the flags, and then runs it.
|
||||
|
68
internal/lsp/cmd/testdata/a/a.go
vendored
Normal file
68
internal/lsp/cmd/testdata/a/a.go
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
package a
|
||||
|
||||
type Thing struct { //@Thing
|
||||
Member string //@Member
|
||||
}
|
||||
|
||||
var Other Thing //@Other
|
||||
|
||||
func Things(val []string) []Thing { //@Things
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Thing) Method(i int) string { //@Method
|
||||
return t.Member
|
||||
}
|
||||
|
||||
func useThings() {
|
||||
t := Thing{} //@mark(aStructType, "ing")
|
||||
fmt.Print(t.Member) //@mark(aMember, "ember")
|
||||
fmt.Print(Other) //@mark(aVar, "ther")
|
||||
Things() //@mark(aFunc, "ings")
|
||||
t.Method() //@mark(aMethod, "eth")
|
||||
}
|
||||
|
||||
/*@
|
||||
definition(aStructType, "", Thing, "$file:$line:$col,$eline:$ecol: defined here as type Thing struct{Member string}")
|
||||
definition(aStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type Thing")
|
||||
|
||||
definition(aMember, "", Member, "$file:$line:$col,$eline:$ecol: defined here as field Member string")
|
||||
definition(aMember, "-emulate=guru", Member, "$file:$line:$col: defined here as field Member string")
|
||||
|
||||
definition(aVar, "", Other, "$file:$line:$col,$eline:$ecol: defined here as var Other Thing")
|
||||
definition(aVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var Other")
|
||||
|
||||
definition(aFunc, "", Things, "$file:$line:$col,$eline:$ecol: defined here as func Things(val []string) []Thing")
|
||||
definition(aFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func Things(val []string) []Thing")
|
||||
|
||||
definition(aMethod, "", Method, "$file:$line:$col,$eline:$ecol: defined here as func (Thing).Method(i int) string")
|
||||
definition(aMethod, "-emulate=guru", Method, "$file:$line:$col: defined here as func (Thing).Method(i int) string")
|
||||
|
||||
//param
|
||||
//package name
|
||||
//const
|
||||
//anon field
|
||||
|
||||
// JSON tests
|
||||
|
||||
definition(aStructType, "-json", Thing, `{
|
||||
"location": {
|
||||
"file": "$efile",
|
||||
"start": {
|
||||
"line": $line,
|
||||
"column": $col,
|
||||
"offset": $offset
|
||||
},
|
||||
"end": {
|
||||
"line": $eline,
|
||||
"column": $ecol,
|
||||
"offset": $eoffset
|
||||
}
|
||||
},
|
||||
"description": "type Thing struct{Member string}"
|
||||
}`)
|
||||
definition(aStructType, "-json -emulate=guru", Thing, `{
|
||||
"objpos": "$efile:$line:$col",
|
||||
"desc": "type Thing"
|
||||
}`)
|
||||
*/
|
26
internal/lsp/cmd/testdata/b/b.go
vendored
Normal file
26
internal/lsp/cmd/testdata/b/b.go
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
package b
|
||||
|
||||
import (
|
||||
"golang.org/fake/a"
|
||||
)
|
||||
|
||||
func useThings() {
|
||||
t := a.Thing{} //@mark(bStructType, "ing")
|
||||
fmt.Print(t.Member) //@mark(bMember, "ember")
|
||||
fmt.Print(a.Other) //@mark(bVar, "ther")
|
||||
a.Things() //@mark(bFunc, "ings")
|
||||
}
|
||||
|
||||
/*@
|
||||
definition(bStructType, "", Thing, "$file:$line:$col,$eline:$ecol: defined here as type a.Thing struct{Member string}")
|
||||
definition(bStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type golang.org/fake/a.Thing")
|
||||
|
||||
definition(bMember, "", Member, "$file:$line:$col,$eline:$ecol: defined here as field Member string")
|
||||
definition(bMember, "-emulate=guru", Member, "$file:$line:$col: defined here as field Member string")
|
||||
|
||||
definition(bVar, "", Other, "$file:$line:$col,$eline:$ecol: defined here as var a.Other a.Thing")
|
||||
definition(bVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var golang.org/fake/a.Other")
|
||||
|
||||
definition(bFunc, "", Things, "$file:$line:$col,$eline:$ecol: defined here as func a.Things(val []string) []a.Thing")
|
||||
definition(bFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func golang.org/fake/a.Things(val []string) []golang.org/fake/a.Thing")
|
||||
*/
|
Loading…
Reference in New Issue
Block a user