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")
+*/