internal/lsp: fix definition tests to use golden files

specifically it uses them for the guru compatability tests
This change radically increases the test coverage of the godef tests as it now
works for all the jump to definition tests not just the specialized ones.

Change-Id: I63547138566ac3de56344dcfddb758ed5f362a06
Reviewed-on: https://go-review.googlesource.com/c/tools/+/174937
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index f91f359..5474238 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -8,10 +8,10 @@
 	"bytes"
 	"io/ioutil"
 	"os"
+	"path/filepath"
+	"strconv"
 	"strings"
 	"testing"
-	"unicode"
-	"unicode/utf8"
 
 	"golang.org/x/tools/go/packages/packagestest"
 	"golang.org/x/tools/internal/lsp/cmd"
@@ -21,8 +21,9 @@
 var isRace = false
 
 type runner struct {
-	data *tests.Data
-	app  *cmd.Application
+	exporter packagestest.Exporter
+	data     *tests.Data
+	app      *cmd.Application
 }
 
 func TestCommandLine(t *testing.T) {
@@ -34,7 +35,8 @@
 	defer data.Exported.Cleanup()
 
 	r := &runner{
-		data: data,
+		exporter: exporter,
+		data:     data,
 		app: &cmd.Application{
 			Config: *data.Exported.Config,
 		},
@@ -84,20 +86,34 @@
 
 // normalizePaths replaces all paths present in s with just the fragment portion
 // this is used to make golden files not depend on the temporary paths of the files
-func (r *runner) normalizePaths(s string) string {
+func normalizePaths(data *tests.Data, s string) string {
 	type entry struct {
-		path  string
-		index int
+		path     string
+		index    int
+		fragment string
 	}
-	match := make([]entry, len(r.data.Exported.Modules))
+	match := make([]entry, 0, len(data.Exported.Modules))
 	// collect the initial state of all the matchers
-	for i, m := range r.data.Exported.Modules {
-		// any random file will do, we collect the first one only
-		for f := range m.Files {
-			path := strings.TrimSuffix(r.data.Exported.File(m.Name, f), f)
-			index := strings.Index(s, path)
-			match[i] = entry{path, index}
-			break
+	for _, m := range data.Exported.Modules {
+		for fragment := range m.Files {
+			filename := data.Exported.File(m.Name, fragment)
+			index := strings.Index(s, filename)
+			if index >= 0 {
+				match = append(match, entry{filename, index, fragment})
+			}
+			if slash := filepath.ToSlash(filename); slash != filename {
+				index := strings.Index(s, slash)
+				if index >= 0 {
+					match = append(match, entry{slash, index, fragment})
+				}
+			}
+			quoted := strconv.Quote(filename)
+			if escaped := quoted[1 : len(quoted)-1]; escaped != filename {
+				index := strings.Index(s, escaped)
+				if index >= 0 {
+					match = append(match, entry{escaped, index, fragment})
+				}
+			}
 		}
 	}
 	// result should be the same or shorter than the input
@@ -122,20 +138,10 @@
 		n := &match[next]
 		// copy up to the start of the match
 		buf.WriteString(s[last:n.index])
-		// skip over the non fragment prefix
+		// skip over the filename
 		last = n.index + len(n.path)
-		// now try to convert the fragment part
-		for last < len(s) {
-			r, size := utf8.DecodeRuneInString(s[last:])
-			if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '/' {
-				buf.WriteRune(r)
-			} else if r == '\\' {
-				buf.WriteRune('/')
-			} else {
-				break
-			}
-			last += size
-		}
+		// add in the fragment instead
+		buf.WriteString(n.fragment)
 		// see what the next match for this path is
 		n.index = strings.Index(s[last:], n.path)
 		if n.index >= 0 {
diff --git a/internal/lsp/cmd/definition_test.go b/internal/lsp/cmd/definition_test.go
index 3e44f9a..e7ba04a 100644
--- a/internal/lsp/cmd/definition_test.go
+++ b/internal/lsp/cmd/definition_test.go
@@ -6,14 +6,12 @@
 
 import (
 	"context"
-	"flag"
 	"fmt"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"regexp"
 	"runtime"
-	"strconv"
 	"strings"
 	"testing"
 
@@ -28,7 +26,20 @@
 	expectedTypeDefinitionsCount = 2
 )
 
-var verifyGuru = flag.Bool("verify-guru", false, "Check that the guru compatability matches")
+type godefMode int
+
+const (
+	plainGodef = godefMode(1 << iota)
+	jsonGoDef
+	guruGoDef
+)
+
+var godefModes = []godefMode{
+	plainGodef,
+	jsonGoDef,
+	guruGoDef,
+	jsonGoDef | guruGoDef,
+}
 
 func TestDefinitionHelpExample(t *testing.T) {
 	if runtime.GOOS == "android" {
@@ -55,132 +66,96 @@
 	}
 }
 
+var brokenDefinitionTests = map[string]bool{
+	// The following tests all have extra information in the description
+	"A-definition-json-guru":            true,
+	"err-definition-json-guru":          true,
+	"myUnclosedIf-definition-json-guru": true,
+	"Other-definition-json-guru":        true,
+	"RandomParamY-definition-json-guru": true,
+	"S1-definition-json-guru":           true,
+	"S2-definition-json-guru":           true,
+	"Stuff-definition-json-guru":        true,
+	"Thing-definition-json-guru":        true,
+	"Things-definition-json-guru":       true,
+}
+
 func (r *runner) Definition(t *testing.T, data tests.Definitions) {
 	for _, d := range data {
 		if d.IsType {
 			// TODO: support type definition queries
 			continue
 		}
-		args := []string{"-remote=internal", "query"}
-		if d.Flags != "" {
-			args = append(args, strings.Split(d.Flags, " ")...)
-		}
-		args = append(args, "definition")
-		src := span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})
-		args = append(args, fmt.Sprint(src))
-		got := captureStdOut(t, func() {
-			tool.Main(context.Background(), r.app, args)
-		})
-		pattern := newPattern(d.Match, d.Def)
-		if !pattern.matches(got) {
-			t.Errorf("definition %v\nexpected:\n%s\ngot:\n%s", args, pattern, got)
-		}
-		if *verifyGuru {
-			moduleMode := r.data.Exported.File(r.data.Exported.Modules[0].Name, "go.mod") != ""
-			var guruArgs []string
-			runGuru := false
-			if !moduleMode {
-				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)
-					}
-				}
+		d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})
+		for _, mode := range godefModes {
+			args := []string{"-remote=internal", "query"}
+			tag := d.Name + "-definition"
+			if mode&jsonGoDef != 0 {
+				tag += "-json"
+				args = append(args, "-json")
 			}
-			if runGuru {
+			if mode&guruGoDef != 0 {
+				if r.exporter.Name() != "GOPATH" {
+					//only run guru compatability tests in GOPATH mode
+					continue
+				}
+				if d.Name == "PackageFoo" {
+					//guru does not support definition on packages
+					continue
+				}
+				tag += "-guru"
+				args = append(args, "-emulate=guru")
+			}
+			if _, found := brokenDefinitionTests[tag]; found {
+				continue
+			}
+			args = append(args, "definition")
+			uri := d.Src.URI()
+			filename, err := uri.Filename()
+			if err != nil {
+				t.Fatal(err)
+			}
+			args = append(args, fmt.Sprint(d.Src))
+			got := captureStdOut(t, func() {
+				tool.Main(context.Background(), r.app, args)
+			})
+			got = normalizePaths(r.data, got)
+			if mode&jsonGoDef != 0 && runtime.GOOS == "windows" {
+				got = strings.Replace(got, "file:///", "file://", -1)
+			}
+			if mode&guruGoDef == 0 {
+				expect := string(r.data.Golden(tag, filename, func() ([]byte, error) {
+					return []byte(got), nil
+				}))
+				if got != expect {
+					t.Errorf("definition %v failed with %#v expected:\n%s\ngot:\n%s", tag, args, expect, got)
+				}
+				continue
+			}
+			guruArgs := []string{}
+			if mode&jsonGoDef != 0 {
+				guruArgs = append(guruArgs, "-json")
+			}
+			guruArgs = append(guruArgs, "definition", fmt.Sprint(d.Src))
+			expect := strings.TrimSpace(string(r.data.Golden(tag, filename, func() ([]byte, error) {
 				cmd := exec.Command("guru", guruArgs...)
 				cmd.Env = r.data.Exported.Config.Env
-				out, err := cmd.CombinedOutput()
+				out, _ := cmd.Output()
 				if err != nil {
-					t.Errorf("Could not run guru %v: %v\n%s", guruArgs, err, out)
-				} else {
-					guru := strings.TrimSpace(string(out))
-					if !pattern.matches(guru) {
-						t.Errorf("definition %v\nexpected:\n%s\nguru gave:\n%s", args, pattern, guru)
+					if _, ok := err.(*exec.ExitError); !ok {
+						return nil, fmt.Errorf("Could not run guru %v: %v\n%s", guruArgs, err, out)
 					}
 				}
+				result := normalizePaths(r.data, string(out))
+				// guru sometimes puts the full package path in type names, but we don't
+				if mode&jsonGoDef == 0 && d.Name != "AImport" {
+					result = strings.Replace(result, "golang.org/x/tools/internal/lsp/godef/", "", -1)
+				}
+				return []byte(result), nil
+			})))
+			if expect != "" && !strings.HasPrefix(got, expect) {
+				t.Errorf("definition %v failed with %#v expected:\n%q\ngot:\n%q", tag, args, expect, got)
 			}
 		}
 	}
 }
-
-type pattern struct {
-	raw      string
-	expanded []string
-	matchAll bool
-}
-
-func newPattern(s string, def span.Span) pattern {
-	p := pattern{raw: s}
-	if s == "" {
-		p.expanded = []string{fmt.Sprintf("%v: ", def)}
-		return p
-	}
-	p.matchAll = strings.HasSuffix(s, "$$")
-	for _, fragment := range strings.Split(s, "$$") {
-		p.expanded = append(p.expanded, os.Expand(fragment, func(name string) string {
-			switch name {
-			case "file":
-				fname, _ := def.URI().Filename()
-				return fname
-			case "efile":
-				fname, _ := def.URI().Filename()
-				qfile := strconv.Quote(fname)
-				return qfile[1 : len(qfile)-1]
-			case "euri":
-				quri := strconv.Quote(string(def.URI()))
-				return quri[1 : len(quri)-1]
-			case "line":
-				return fmt.Sprint(def.Start().Line())
-			case "col":
-				return fmt.Sprint(def.Start().Column())
-			case "offset":
-				return fmt.Sprint(def.Start().Offset())
-			case "eline":
-				return fmt.Sprint(def.End().Line())
-			case "ecol":
-				return fmt.Sprint(def.End().Column())
-			case "eoffset":
-				return fmt.Sprint(def.End().Offset())
-			default:
-				return name
-			}
-		}))
-	}
-	return p
-}
-
-func (p pattern) String() string {
-	return strings.Join(p.expanded, "$$")
-}
-
-func (p pattern) matches(s string) bool {
-	if len(p.expanded) == 0 {
-		return false
-	}
-	if !strings.HasPrefix(s, p.expanded[0]) {
-		return false
-	}
-	remains := s[len(p.expanded[0]):]
-	for _, fragment := range p.expanded[1:] {
-		i := strings.Index(remains, fragment)
-		if i < 0 {
-			return false
-		}
-		remains = remains[i+len(fragment):]
-	}
-	if !p.matchAll {
-		return true
-	}
-	return len(remains) == 0
-}
diff --git a/internal/lsp/cmd/format_test.go b/internal/lsp/cmd/format_test.go
index 8e38075..5674fa4 100644
--- a/internal/lsp/cmd/format_test.go
+++ b/internal/lsp/cmd/format_test.go
@@ -34,7 +34,7 @@
 			expect := string(r.data.Golden(tag, filename, func() ([]byte, error) {
 				cmd := exec.Command("gofmt", args...)
 				contents, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
-				contents = []byte(r.normalizePaths(fixFileHeader(string(contents))))
+				contents = []byte(normalizePaths(r.data, fixFileHeader(string(contents))))
 				return contents, nil
 			}))
 			if expect == "" {
@@ -46,7 +46,7 @@
 			got := captureStdOut(t, func() {
 				tool.Main(context.Background(), app, append([]string{"-remote=internal", "format"}, args...))
 			})
-			got = r.normalizePaths(got)
+			got = normalizePaths(r.data, got)
 			// check the first two lines are the expected file header
 			if expect != got {
 				t.Errorf("format failed with %#v expected:\n%s\ngot:\n%s", args, expect, got)
diff --git a/internal/lsp/reset_golden.sh b/internal/lsp/reset_golden.sh
index d52ee42..9439ed0 100755
--- a/internal/lsp/reset_golden.sh
+++ b/internal/lsp/reset_golden.sh
@@ -1,4 +1,4 @@
 #!/bin/bash
 
 find ./internal/lsp/ -name *.golden -delete
-go test ./internal/lsp/ ./internal/lsp/cmd -golden
+go test ./internal/lsp/ ./internal/lsp/cmd ./internal/lsp/source -golden
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/godef/a/a.go.golden
index be39735..c3cc488 100644
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ b/internal/lsp/testdata/godef/a/a.go.golden
@@ -1,6 +1,82 @@
+-- Random-definition --
+godef/a/random.go:3:6-12: defined here as func Random() int
+-- Random-definition-guru --
+
+-- Random-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/random.go",
+		"start": {
+			"line": 3,
+			"column": 6,
+			"offset": 16
+		},
+		"end": {
+			"line": 3,
+			"column": 12,
+			"offset": 22
+		}
+	},
+	"description": "func Random() int"
+}
+
+-- Random-definition-json-guru --
+
 -- Random-hover --
 func Random() int
+-- Random2-definition --
+godef/a/random.go:8:6-13: defined here as func Random2(y int) int
+-- Random2-definition-guru --
+godef/a/random.go:8:6: defined here as func Random2(y int) int
+
+-- Random2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/random.go",
+		"start": {
+			"line": 8,
+			"column": 6,
+			"offset": 71
+		},
+		"end": {
+			"line": 8,
+			"column": 13,
+			"offset": 78
+		}
+	},
+	"description": "func Random2(y int) int"
+}
+
+-- Random2-definition-json-guru --
+{
+	"objpos": "godef/a/random.go:8:6",
+	"desc": "func Random2(y int) int"
+}
+
 -- Random2-hover --
 func Random2(y int) int
+-- err-definition --
+godef/a/a.go:14:6-9: defined here as var err error
+-- err-definition-guru --
+godef/a/a.go:14:6: defined here as var err
+
+-- err-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a.go",
+		"start": {
+			"line": 14,
+			"column": 6,
+			"offset": 201
+		},
+		"end": {
+			"line": 14,
+			"column": 9,
+			"offset": 204
+		}
+	},
+	"description": "var err error"
+}
+
 -- err-hover --
 var err error
diff --git a/internal/lsp/testdata/godef/a/d.go b/internal/lsp/testdata/godef/a/d.go
index 0b1498e..4025788 100644
--- a/internal/lsp/testdata/godef/a/d.go
+++ b/internal/lsp/testdata/godef/a/d.go
@@ -25,46 +25,15 @@
 }
 
 /*@
-definition(aStructType, "", Thing, "$file:$line:$col-$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-$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-$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-$ecol: defined here as func Things(val []string) []Thing")
-definition(aFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func Things")
-
-definition(aMethod, "", Method, "$file:$line:$col-$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")
+godef(aStructType, Thing)
+godef(aMember, Member)
+godef(aVar, Other)
+godef(aFunc, Things)
+godef(aMethod, Method)
 
 //param
 //package name
 //const
 //anon field
 
-// JSON tests
-
-definition(aStructType, "-json", Thing, `{
-	"span": {
-		"uri": "$euri",
-		"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/testdata/godef/a/d.go.golden b/internal/lsp/testdata/godef/a/d.go.golden
new file mode 100644
index 0000000..6e3a2bd
--- /dev/null
+++ b/internal/lsp/testdata/godef/a/d.go.golden
@@ -0,0 +1,137 @@
+-- Member-definition --
+godef/a/d.go:6:2-8: defined here as field Member string
+-- Member-definition-guru --
+godef/a/d.go:6:2: defined here as field Member string
+
+-- Member-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 6,
+			"column": 2,
+			"offset": 55
+		},
+		"end": {
+			"line": 6,
+			"column": 8,
+			"offset": 61
+		}
+	},
+	"description": "field Member string"
+}
+
+-- Member-definition-json-guru --
+{
+	"objpos": "godef/a/d.go:6:2",
+	"desc": "field Member string"
+}
+
+-- Member-hover --
+field Member string
+-- Method-definition --
+godef/a/d.go:15:16-22: defined here as func (Thing).Method(i int) string
+-- Method-definition-guru --
+godef/a/d.go:15:16: defined here as func (Thing).Method(i int) string
+
+-- Method-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 15,
+			"column": 16,
+			"offset": 184
+		},
+		"end": {
+			"line": 15,
+			"column": 22,
+			"offset": 190
+		}
+	},
+	"description": "func (Thing).Method(i int) string"
+}
+
+-- Method-definition-json-guru --
+{
+	"objpos": "godef/a/d.go:15:16",
+	"desc": "func (Thing).Method(i int) string"
+}
+
+-- Method-hover --
+func (Thing).Method(i int) string
+-- Other-definition --
+godef/a/d.go:9:5-10: defined here as var Other Thing
+-- Other-definition-guru --
+godef/a/d.go:9:5: defined here as var Other
+
+-- Other-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 9,
+			"column": 5,
+			"offset": 86
+		},
+		"end": {
+			"line": 9,
+			"column": 10,
+			"offset": 91
+		}
+	},
+	"description": "var Other Thing"
+}
+
+-- Other-hover --
+var Other Thing
+-- Thing-definition --
+godef/a/d.go:5:6-11: defined here as type Thing struct{Member string}
+-- Thing-definition-guru --
+godef/a/d.go:5:6: defined here as type Thing
+
+-- Thing-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 5,
+			"column": 6,
+			"offset": 30
+		},
+		"end": {
+			"line": 5,
+			"column": 11,
+			"offset": 35
+		}
+	},
+	"description": "type Thing struct{Member string}"
+}
+
+-- Thing-hover --
+type Thing struct{Member string}
+-- Things-definition --
+godef/a/d.go:11:6-12: defined here as func Things(val []string) []Thing
+-- Things-definition-guru --
+godef/a/d.go:11:6: defined here as func Things
+
+-- Things-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 11,
+			"column": 6,
+			"offset": 113
+		},
+		"end": {
+			"line": 11,
+			"column": 12,
+			"offset": 119
+		}
+	},
+	"description": "func Things(val []string) []Thing"
+}
+
+-- Things-hover --
+func Things(val []string) []Thing
diff --git a/internal/lsp/testdata/godef/a/random.go.golden b/internal/lsp/testdata/godef/a/random.go.golden
index b702ee8..077b710 100644
--- a/internal/lsp/testdata/godef/a/random.go.golden
+++ b/internal/lsp/testdata/godef/a/random.go.golden
@@ -1,6 +1,82 @@
+-- PosSum-definition --
+godef/a/random.go:16:15-18: defined here as func (*Pos).Sum() int
+-- PosSum-definition-guru --
+
+-- PosSum-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/random.go",
+		"start": {
+			"line": 16,
+			"column": 15,
+			"offset": 248
+		},
+		"end": {
+			"line": 16,
+			"column": 18,
+			"offset": 251
+		}
+	},
+	"description": "func (*Pos).Sum() int"
+}
+
+-- PosSum-definition-json-guru --
+
 -- PosSum-hover --
 func (*Pos).Sum() int
+-- PosX-definition --
+godef/a/random.go:13:2-3: defined here as field x int
+-- PosX-definition-guru --
+godef/a/random.go:13:2: defined here as field x int
+
+-- PosX-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/random.go",
+		"start": {
+			"line": 13,
+			"column": 2,
+			"offset": 187
+		},
+		"end": {
+			"line": 13,
+			"column": 3,
+			"offset": 188
+		}
+	},
+	"description": "field x int"
+}
+
+-- PosX-definition-json-guru --
+{
+	"objpos": "godef/a/random.go:13:2",
+	"desc": "field x int"
+}
+
 -- PosX-hover --
 field x int
+-- RandomParamY-definition --
+godef/a/random.go:8:14-15: defined here as var y int
+-- RandomParamY-definition-guru --
+godef/a/random.go:8:14: defined here as var y
+
+-- RandomParamY-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/random.go",
+		"start": {
+			"line": 8,
+			"column": 14,
+			"offset": 79
+		},
+		"end": {
+			"line": 8,
+			"column": 15,
+			"offset": 80
+		}
+	},
+	"description": "var y int"
+}
+
 -- RandomParamY-hover --
 var y int
diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden
index 5fae427..556337e 100644
--- a/internal/lsp/testdata/godef/b/b.go.golden
+++ b/internal/lsp/testdata/godef/b/b.go.golden
@@ -1,20 +1,277 @@
+-- A-definition --
+godef/a/a.go:7:6-7: defined here as type a.A string
+-- A-definition-guru --
+godef/a/a.go:7:6: defined here as type a.A
+
+-- A-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a.go",
+		"start": {
+			"line": 7,
+			"column": 6,
+			"offset": 75
+		},
+		"end": {
+			"line": 7,
+			"column": 7,
+			"offset": 76
+		}
+	},
+	"description": "type a.A string"
+}
+
 -- A-hover --
 type a.A string
+-- AImport-definition --
+godef/b/b.go:5:2-3: defined here as package a ("golang.org/x/tools/internal/lsp/godef/a")
+-- AImport-definition-guru --
+godef/b/b.go:5:2: defined here as package a ("golang.org/x/tools/internal/lsp/godef/a")
+
+-- AImport-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 5,
+			"column": 2,
+			"offset": 121
+		},
+		"end": {
+			"line": 5,
+			"column": 3,
+			"offset": 122
+		}
+	},
+	"description": "package a (\"golang.org/x/tools/internal/lsp/godef/a\")"
+}
+
+-- AImport-definition-json-guru --
+{
+	"objpos": "godef/b/b.go:5:2",
+	"desc": "package a (\"golang.org/x/tools/internal/lsp/godef/a\")"
+}
+
 -- AImport-hover --
 package a ("golang.org/x/tools/internal/lsp/godef/a")
+-- PackageFoo-definition --
+foo/foo.go:1:9-12: defined here as 
+-- PackageFoo-definition-json --
+{
+	"span": {
+		"uri": "file://foo/foo.go",
+		"start": {
+			"line": 1,
+			"column": 9,
+			"offset": 8
+		},
+		"end": {
+			"line": 1,
+			"column": 12,
+			"offset": 11
+		}
+	},
+	"description": ""
+}
+
 -- PackageFoo-hover --
 
+-- S1-definition --
+godef/b/b.go:8:6-8: defined here as type S1 struct{F1 int; S2; a.A}
+-- S1-definition-guru --
+godef/b/b.go:8:6: defined here as type S1
+
+-- S1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 8,
+			"column": 6,
+			"offset": 196
+		},
+		"end": {
+			"line": 8,
+			"column": 8,
+			"offset": 198
+		}
+	},
+	"description": "type S1 struct{F1 int; S2; a.A}"
+}
+
 -- S1-hover --
 type S1 struct{F1 int; S2; a.A}
+-- S1F1-definition --
+godef/b/b.go:9:2-4: defined here as field F1 int
+-- S1F1-definition-guru --
+godef/b/b.go:9:2: defined here as field F1 int
+
+-- S1F1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 9,
+			"column": 2,
+			"offset": 215
+		},
+		"end": {
+			"line": 9,
+			"column": 4,
+			"offset": 217
+		}
+	},
+	"description": "field F1 int"
+}
+
+-- S1F1-definition-json-guru --
+{
+	"objpos": "godef/b/b.go:9:2",
+	"desc": "field F1 int"
+}
+
 -- S1F1-hover --
 field F1 int
+-- S1S2-definition --
+godef/b/b.go:10:2-4: defined here as field S2 S2
+-- S1S2-definition-guru --
+godef/b/b.go:10:2: defined here as field S2 S2
+
+-- S1S2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 10,
+			"column": 2,
+			"offset": 244
+		},
+		"end": {
+			"line": 10,
+			"column": 4,
+			"offset": 246
+		}
+	},
+	"description": "field S2 S2"
+}
+
+-- S1S2-definition-json-guru --
+{
+	"objpos": "godef/b/b.go:10:2",
+	"desc": "field S2 S2"
+}
+
 -- S1S2-hover --
 field S2 S2
+-- S2-definition --
+godef/b/b.go:14:6-8: defined here as type S2 struct{F1 string; F2 int; *a.A}
+-- S2-definition-guru --
+godef/b/b.go:14:6: defined here as type S2
+
+-- S2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 14,
+			"column": 6,
+			"offset": 323
+		},
+		"end": {
+			"line": 14,
+			"column": 8,
+			"offset": 325
+		}
+	},
+	"description": "type S2 struct{F1 string; F2 int; *a.A}"
+}
+
 -- S2-hover --
 type S2 struct{F1 string; F2 int; *a.A}
+-- S2F1-definition --
+godef/b/b.go:15:2-4: defined here as field F1 string
+-- S2F1-definition-guru --
+godef/b/b.go:15:2: defined here as field F1 string
+
+-- S2F1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 15,
+			"column": 2,
+			"offset": 342
+		},
+		"end": {
+			"line": 15,
+			"column": 4,
+			"offset": 344
+		}
+	},
+	"description": "field F1 string"
+}
+
+-- S2F1-definition-json-guru --
+{
+	"objpos": "godef/b/b.go:15:2",
+	"desc": "field F1 string"
+}
+
 -- S2F1-hover --
 field F1 string
+-- S2F2-definition --
+godef/b/b.go:16:2-4: defined here as field F2 int
+-- S2F2-definition-guru --
+godef/b/b.go:16:2: defined here as field F2 int
+
+-- S2F2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 16,
+			"column": 2,
+			"offset": 375
+		},
+		"end": {
+			"line": 16,
+			"column": 4,
+			"offset": 377
+		}
+	},
+	"description": "field F2 int"
+}
+
+-- S2F2-definition-json-guru --
+{
+	"objpos": "godef/b/b.go:16:2",
+	"desc": "field F2 int"
+}
+
 -- S2F2-hover --
 field F2 int
+-- Stuff-definition --
+godef/a/a.go:9:6-11: defined here as func a.Stuff()
+-- Stuff-definition-guru --
+godef/a/a.go:9:6: defined here as func a.Stuff
+
+-- Stuff-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a.go",
+		"start": {
+			"line": 9,
+			"column": 6,
+			"offset": 95
+		},
+		"end": {
+			"line": 9,
+			"column": 11,
+			"offset": 100
+		}
+	},
+	"description": "func a.Stuff()"
+}
+
 -- Stuff-hover --
 func a.Stuff()
diff --git a/internal/lsp/testdata/godef/b/c.go.golden b/internal/lsp/testdata/godef/b/c.go.golden
index 6d86d73..69a14eb 100644
--- a/internal/lsp/testdata/godef/b/c.go.golden
+++ b/internal/lsp/testdata/godef/b/c.go.golden
@@ -1,4 +1,56 @@
+-- S1-definition --
+godef/b/b.go:8:6-8: defined here as type S1 struct{F1 int; S2; a.A}
+-- S1-definition-guru --
+godef/b/b.go:8:6: defined here as type S1 struct{F1 int; S2; a.A}
+
+-- S1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 8,
+			"column": 6,
+			"offset": 196
+		},
+		"end": {
+			"line": 8,
+			"column": 8,
+			"offset": 198
+		}
+	},
+	"description": "type S1 struct{F1 int; S2; a.A}"
+}
+
 -- S1-hover --
 type S1 struct{F1 int; S2; a.A}
+-- S1F1-definition --
+godef/b/b.go:9:2-4: defined here as field F1 int
+-- S1F1-definition-guru --
+godef/b/b.go:9:2: defined here as field F1 int
+
+-- S1F1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 9,
+			"column": 2,
+			"offset": 215
+		},
+		"end": {
+			"line": 9,
+			"column": 4,
+			"offset": 217
+		}
+	},
+	"description": "field F1 int"
+}
+
+-- S1F1-definition-json-guru --
+{
+	"objpos": "godef/b/b.go:9:2",
+	"desc": "field F1 int"
+}
+
 -- S1F1-hover --
 field F1 int
diff --git a/internal/lsp/testdata/godef/b/e.go b/internal/lsp/testdata/godef/b/e.go
index 6b2337a..dc8fe6e 100644
--- a/internal/lsp/testdata/godef/b/e.go
+++ b/internal/lsp/testdata/godef/b/e.go
@@ -14,15 +14,8 @@
 }
 
 /*@
-definition(bStructType, "", Thing, "$file:$line:$col-$ecol: defined here as type a.Thing struct{Member string}")
-definition(bStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type $$a.Thing")
-
-definition(bMember, "", Member, "$file:$line:$col-$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-$ecol: defined here as var a.Other a.Thing")
-definition(bVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var $$a.Other")
-
-definition(bFunc, "", Things, "$file:$line:$col-$ecol: defined here as func a.Things(val []string) []a.Thing")
-definition(bFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func $$a.Things")
+godef(bStructType, Thing)
+godef(bMember, Member)
+godef(bVar, Other)
+godef(bFunc, Things)
 */
diff --git a/internal/lsp/testdata/godef/b/e.go.golden b/internal/lsp/testdata/godef/b/e.go.golden
new file mode 100644
index 0000000..5db0e4c
--- /dev/null
+++ b/internal/lsp/testdata/godef/b/e.go.golden
@@ -0,0 +1,106 @@
+-- Member-definition --
+godef/a/d.go:6:2-8: defined here as field Member string
+-- Member-definition-guru --
+godef/a/d.go:6:2: defined here as field Member string
+
+-- Member-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 6,
+			"column": 2,
+			"offset": 55
+		},
+		"end": {
+			"line": 6,
+			"column": 8,
+			"offset": 61
+		}
+	},
+	"description": "field Member string"
+}
+
+-- Member-definition-json-guru --
+{
+	"objpos": "godef/a/d.go:6:2",
+	"desc": "field Member string"
+}
+
+-- Member-hover --
+field Member string
+-- Other-definition --
+godef/a/d.go:9:5-10: defined here as var a.Other a.Thing
+-- Other-definition-guru --
+godef/a/d.go:9:5: defined here as var a.Other
+
+-- Other-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 9,
+			"column": 5,
+			"offset": 86
+		},
+		"end": {
+			"line": 9,
+			"column": 10,
+			"offset": 91
+		}
+	},
+	"description": "var a.Other a.Thing"
+}
+
+-- Other-hover --
+var a.Other a.Thing
+-- Thing-definition --
+godef/a/d.go:5:6-11: defined here as type a.Thing struct{Member string}
+-- Thing-definition-guru --
+godef/a/d.go:5:6: defined here as type a.Thing
+
+-- Thing-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 5,
+			"column": 6,
+			"offset": 30
+		},
+		"end": {
+			"line": 5,
+			"column": 11,
+			"offset": 35
+		}
+	},
+	"description": "type a.Thing struct{Member string}"
+}
+
+-- Thing-hover --
+type a.Thing struct{Member string}
+-- Things-definition --
+godef/a/d.go:11:6-12: defined here as func a.Things(val []string) []a.Thing
+-- Things-definition-guru --
+godef/a/d.go:11:6: defined here as func a.Things
+
+-- Things-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/d.go",
+		"start": {
+			"line": 11,
+			"column": 6,
+			"offset": 113
+		},
+		"end": {
+			"line": 11,
+			"column": 12,
+			"offset": 119
+		}
+	},
+	"description": "func a.Things(val []string) []a.Thing"
+}
+
+-- Things-hover --
+func a.Things(val []string) []a.Thing
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
index 47af586..e49297a 100644
--- a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
+++ b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
@@ -1,2 +1,25 @@
+-- myUnclosedIf-definition --
+godef/broken/unclosedIf.go:7:7-19: defined here as var myUnclosedIf string
+-- myUnclosedIf-definition-guru --
+godef/broken/unclosedIf.go:7:7: defined here as var myUnclosedIf
+
+-- myUnclosedIf-definition-json --
+{
+	"span": {
+		"uri": "file://godef/broken/unclosedIf.go",
+		"start": {
+			"line": 7,
+			"column": 7,
+			"offset": 68
+		},
+		"end": {
+			"line": 7,
+			"column": 19,
+			"offset": 80
+		}
+	},
+	"description": "var myUnclosedIf string"
+}
+
 -- myUnclosedIf-hover --
 var myUnclosedIf string
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 595ebcc..e099f69 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -31,7 +31,7 @@
 	ExpectedCompletionsCount       = 97
 	ExpectedDiagnosticsCount       = 17
 	ExpectedFormatCount            = 5
-	ExpectedDefinitionsCount       = 24
+	ExpectedDefinitionsCount       = 33
 	ExpectedTypeDefinitionsCount   = 2
 	ExpectedHighlightsCount        = 2
 	ExpectedSymbolsCount           = 1
@@ -97,9 +97,7 @@
 	Name   string
 	Src    span.Span
 	IsType bool
-	Flags  string
 	Def    span.Span
-	Match  string
 }
 
 type CompletionSnippet struct {