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>
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 9bf80dd..b054dcf 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -11,39 +11,45 @@
"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 @@
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},
}
}
diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go
new file mode 100644
index 0000000..861de83
--- /dev/null
+++ b/internal/lsp/cmd/definition.go
@@ -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
+}
diff --git a/internal/lsp/cmd/definition_test.go b/internal/lsp/cmd/definition_test.go
new file mode 100644
index 0000000..d02fab2
--- /dev/null
+++ b/internal/lsp/cmd/definition_test.go
@@ -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))
+}
diff --git a/internal/lsp/cmd/export_test.go b/internal/lsp/cmd/export_test.go
new file mode 100644
index 0000000..4377c8d
--- /dev/null
+++ b/internal/lsp/cmd/export_test.go
@@ -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
diff --git a/internal/lsp/cmd/location.go b/internal/lsp/cmd/location.go
new file mode 100644
index 0000000..35e2580
--- /dev/null
+++ b/internal/lsp/cmd/location.go
@@ -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)
+}
diff --git a/internal/lsp/cmd/query.go b/internal/lsp/cmd/query.go
new file mode 100644
index 0000000..3d0bf30
--- /dev/null
+++ b/internal/lsp/cmd/query.go
@@ -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},
+ }
+}
diff --git a/internal/lsp/cmd/server.go b/internal/lsp/cmd/server.go
index dc9a2c4..e205052 100644
--- a/internal/lsp/cmd/server.go
+++ b/internal/lsp/cmd/server.go
@@ -38,7 +38,10 @@
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.
diff --git a/internal/lsp/cmd/testdata/a/a.go b/internal/lsp/cmd/testdata/a/a.go
new file mode 100644
index 0000000..2336434
--- /dev/null
+++ b/internal/lsp/cmd/testdata/a/a.go
@@ -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"
+}`)
+*/
diff --git a/internal/lsp/cmd/testdata/b/b.go b/internal/lsp/cmd/testdata/b/b.go
new file mode 100644
index 0000000..bd197de
--- /dev/null
+++ b/internal/lsp/cmd/testdata/b/b.go
@@ -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")
+*/