diff --git a/gopls/internal/lsp/fake/editor.go b/gopls/internal/lsp/fake/editor.go index e5bc9ba8e5e..84d7fb85fda 100644 --- a/gopls/internal/lsp/fake/editor.go +++ b/gopls/internal/lsp/fake/editor.go @@ -774,6 +774,9 @@ func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty // GoToDefinition jumps to the definition of the symbol at the given position // in an open buffer. It returns the location of the resulting jump. +// +// TODO(rfindley): rename to "Definition", to be consistent with LSP +// terminology. func (e *Editor) GoToDefinition(ctx context.Context, loc protocol.Location) (protocol.Location, error) { if err := e.checkBufferLocation(loc); err != nil { return protocol.Location{}, err diff --git a/gopls/internal/lsp/regtest/expectation.go b/gopls/internal/lsp/regtest/expectation.go index 9c50b294661..9d9f023d92a 100644 --- a/gopls/internal/lsp/regtest/expectation.go +++ b/gopls/internal/lsp/regtest/expectation.go @@ -171,6 +171,26 @@ func ReadDiagnostics(fileName string, into *protocol.PublishDiagnosticsParams) E } } +// ReadAllDiagnostics is an expectation that stores all published diagnostics +// into the provided map, whenever it is evaluated. +// +// It can be used in combination with OnceMet or AfterChange to capture the +// state of diagnostics when other expectations are satisfied. +func ReadAllDiagnostics(into *map[string]*protocol.PublishDiagnosticsParams) Expectation { + check := func(s State) Verdict { + allDiags := make(map[string]*protocol.PublishDiagnosticsParams) + for name, diags := range s.diagnostics { + allDiags[name] = diags + } + *into = allDiags + return Met + } + return Expectation{ + Check: check, + Description: "read all diagnostics", + } +} + // NoOutstandingWork asserts that there is no work initiated using the LSP // $/progress API that has not completed. func NoOutstandingWork() Expectation { diff --git a/gopls/internal/lsp/regtest/marker.go b/gopls/internal/lsp/regtest/marker.go index 768c59cf111..1bba223ae70 100644 --- a/gopls/internal/lsp/regtest/marker.go +++ b/gopls/internal/lsp/regtest/marker.go @@ -7,6 +7,7 @@ package regtest import ( "bytes" "context" + "encoding/json" "flag" "fmt" "go/token" @@ -14,6 +15,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "sort" "strings" "testing" @@ -91,49 +93,44 @@ var updateGolden = flag.Bool("update", false, "if set, update test data during m // - "flags": this file is parsed as flags configuring the MarkerTest // instance. For example, -min_go=go1.18 sets the minimum required Go version // for the test. -// - "settings.json": (*) this file is parsed as JSON, and used as the +// - "settings.json": this file is parsed as JSON, and used as the // session configuration (see gopls/doc/settings.md) // - Golden files: Within the archive, file names starting with '@' are // treated as "golden" content, and are not written to disk, but instead are // made available to test methods expecting an argument of type *Golden, // using the identifier following '@'. For example, if the first parameter of -// Foo were of type *Golden, the test runner would coerce the identifier a in -// the call @foo(a, "b", 3) into a *Golden by collecting golden file data -// starting with "@a/". +// Foo were of type *Golden, the test runner would convert the identifier a +// in the call @foo(a, "b", 3) into a *Golden by collecting golden file +// data starting with "@a/". // // # Marker types // // The following markers are supported within marker tests: -// - @diag(location, regexp): (***) see Special markers below. -// - @hover(src, dst location, g Golden): perform a textDocument/hover at the -// src location, and check that the result spans the dst location, with hover +// - diag(location, regexp): specifies an expected diagnostic matching the +// given regexp at the given location. The test runner requires +// a 1:1 correspondence between observed diagnostics and diag annotations +// - def(src, dst location): perform a textDocument/definition request at +// the src location, and check the the result points to the dst location. +// - hover(src, dst location, g Golden): perform a textDocument/hover at the +// src location, and checks that the result is the dst location, with hover // content matching "hover.md" in the golden data g. -// - @loc(name, location): (**) see [Special markers] below. +// - loc(name, location): specifies the name of a location in the source. These +// locations may be referenced by other markers. // // # Argument conversion // -// In additon to passing through literals as basic types, the marker test -// runner supports the following coercions into non-basic types: -// - string->regexp: strings are parsed as regular expressions -// - string->location: strings are parsed as regular expressions and used to -// match the first location in the line preceding the note -// - name->location: identifiers may reference named locations created using -// the @loc marker. -// - name->Golden: identifiers match the golden content contained in archive -// files prefixed by @. -// -// # Special markers -// -// There are two markers that have additional special handling, rather than -// just invoking the test method of the same name: -// - @loc(name, location): (**) specifies a named location in the source. These -// locations may be referenced by other markers. -// - @diag(location, regexp): (***) specifies an expected diagnostic -// matching the given regexp at the given location. The test runner requires -// a 1:1 correspondence between observed diagnostics and diag annotations: -// it is an error if the test runner receives a publishDiagnostics -// notification for a diagnostic that is not annotated, or if a diagnostic -// annotation does not match an existing diagnostic. +// In additon to the types supported by go/expect, the marker test runner +// applies the following argument conversions from argument type to parameter +// type: +// - string->regexp: the argument parsed as a regular expressions +// - string->location: the location of the first instance of the +// argument in the partial line preceding the note +// - regexp->location: the location of the first match for the argument in +// the partial line preceding the note If the regular expression contains +// exactly one subgroup, the position of the subgroup is used rather than the +// position of the submatch. +// - name->location: the named location corresponding to the argument +// - name->Golden: the golden content prefixed by @ // // # Example // @@ -183,6 +180,8 @@ var updateGolden = flag.Bool("update", false, "if set, update test data during m // at Go tip. Each test function can normalize golden content for older Go // versions. // +// -update does not cause missing @diag markers to be added. +// // # TODO // // This API is a work-in-progress, as we migrate existing marker tests from @@ -190,10 +189,6 @@ var updateGolden = flag.Bool("update", false, "if set, update test data during m // // Remaining TODO: // - parallelize/optimize test execution -// - actually support regexp locations? -// - (*) add support for per-test editor settings (via a settings.json file) -// - (**) add support for locs -// - (***) add special handling for diagnostics // - add support for per-test environment? // - reorganize regtest packages (and rename to just 'test'?) // @@ -251,10 +246,16 @@ func RunMarkerTests(t *testing.T, dir string) { testenv.NeedsGo1Point(t, 18) } test.executed = true - env := newEnv(t, cache, test.files) + c := &markerContext{ + test: test, + env: newEnv(t, cache, test.files, test.settings), + + locations: make(map[expect.Identifier]protocol.Location), + diags: make(map[protocol.Location][]protocol.Diagnostic), + } // TODO(rfindley): make it easier to clean up the regtest environment. - defer env.Editor.Shutdown(context.Background()) // ignore error - defer env.Sandbox.Close() // ignore error + defer c.env.Editor.Shutdown(context.Background()) // ignore error + defer c.env.Sandbox.Close() // ignore error // Open all files so that we operate consistently with LSP clients, and // (pragmatically) so that we have a Mapper available via the fake @@ -262,45 +263,55 @@ func RunMarkerTests(t *testing.T, dir string) { // // This also allows avoiding mutating the editor state in tests. for file := range test.files { - env.OpenFile(file) + c.env.OpenFile(file) } - // Invoke each method in the test. + // Pre-process locations. + var notes []*expect.Note for _, note := range test.notes { - posn := safetoken.StartPosition(test.fset, note.Pos) + switch note.Name { + case "loc": + mi := markers[note.Name] + if err := runMarker(c, mi, note); err != nil { + t.Error(err) + } + default: + notes = append(notes, note) + } + } + + // Wait for the didOpen notifications to be processed, then collect + // diagnostics. + var diags map[string]*protocol.PublishDiagnosticsParams + c.env.AfterChange(ReadAllDiagnostics(&diags)) + for path, params := range diags { + uri := c.env.Sandbox.Workdir.URI(path) + for _, diag := range params.Diagnostics { + loc := protocol.Location{ + URI: uri, + Range: diag.Range, + } + c.diags[loc] = append(c.diags[loc], diag) + } + } + + // Invoke each remaining note function in the test. + for _, note := range notes { mi, ok := markers[note.Name] if !ok { + posn := safetoken.StartPosition(test.fset, note.Pos) t.Errorf("%s: no marker function named %s", posn, note.Name) continue } - - // The first converter corresponds to the *Env argument. All others - // must be coerced from the marker syntax. - if got, want := len(note.Args), len(mi.converters); got != want { - t.Errorf("%s: got %d argumentsto %s, expect %d", posn, got, note.Name, want) - continue - } - - args := []reflect.Value{reflect.ValueOf(env)} - hasErrors := false - for i, in := range note.Args { - // Special handling for the blank identifier: treat it as the zero - // value. - if ident, ok := in.(expect.Identifier); ok && ident == "_" { - zero := reflect.Zero(mi.paramTypes[i]) - args = append(args, zero) - continue - } - out, err := mi.converters[i](env, test, note, in) - if err != nil { - t.Errorf("%s: converting argument #%d of %s (%v): %v", posn, i, note.Name, in, err) - hasErrors = true - } - args = append(args, reflect.ValueOf(out)) + if err := runMarker(c, mi, note); err != nil { + t.Error(err) } + } - if !hasErrors { - mi.fn.Call(args) + // Any remaining (un-eliminated) diagnostics are an error. + for loc, diags := range c.diags { + for _, diag := range diags { + t.Errorf("%s: unexpected diagnostic: %q", c.fmtLoc(loc), diag.Message) } } }) @@ -317,15 +328,47 @@ func RunMarkerTests(t *testing.T, dir string) { } } -// supported markers, excluding @loc and @diag which are handled separately. +// runMarker calls mi.fn with the arguments coerced from note. +func runMarker(c *markerContext, mi markerInfo, note *expect.Note) error { + posn := safetoken.StartPosition(c.test.fset, note.Pos) + // The first converter corresponds to the *Env argument. All others + // must be coerced from the marker syntax. + if got, want := len(note.Args), len(mi.converters); got != want { + return fmt.Errorf("%s: got %d arguments to %s, expect %d", posn, got, note.Name, want) + } + + args := []reflect.Value{reflect.ValueOf(c)} + for i, in := range note.Args { + // Special handling for the blank identifier: treat it as the zero + // value. + if ident, ok := in.(expect.Identifier); ok && ident == "_" { + zero := reflect.Zero(mi.paramTypes[i]) + args = append(args, zero) + continue + } + out, err := mi.converters[i](c, note, in) + if err != nil { + return fmt.Errorf("%s: converting argument #%d of %s (%v): %v", posn, i, note.Name, in, err) + } + args = append(args, reflect.ValueOf(out)) + } + + mi.fn.Call(args) + return nil +} + +// Supported markers. // -// Each marker func must accept an *Env as its first argument, with subsequent -// arguments coerced from the arguments to the marker annotation. +// Each marker func must accept an markerContext as its first argument, with +// subsequent arguments coerced from the marker arguments. // // Marker funcs should not mutate the test environment (e.g. via opening files // or applying edits in the editor). var markers = map[string]markerInfo{ + "def": makeMarker(defMarker), + "diag": makeMarker(diagMarker), "hover": makeMarker(hoverMarker), + "loc": makeMarker(locMarker), } // MarkerTest holds all the test data extracted from a test txtar archive. @@ -333,11 +376,13 @@ var markers = map[string]markerInfo{ // See the documentation for RunMarkerTests for more information on the archive // format. type MarkerTest struct { - name string // relative path to the txtar file in the testdata dir - fset *token.FileSet // fileset used for parsing notes - files map[string][]byte - notes []*expect.Note - golden map[string]*Golden + name string // relative path to the txtar file in the testdata dir + fset *token.FileSet // fileset used for parsing notes + archive *txtar.Archive // original test archive + settings map[string]interface{} // gopls settings + files map[string][]byte // data files from the archive (excluding special files) + notes []*expect.Note // extracted notes from data files + golden map[string]*Golden // extracted golden content, by identifier name // executed tracks whether the test was executed. // @@ -346,7 +391,6 @@ type MarkerTest struct { // flags holds flags extracted from the special "flags" archive file. flags []string - // Parsed flags values. minGoVersion string } @@ -364,6 +408,7 @@ func (t *MarkerTest) flagSet() *flag.FlagSet { // When -update is set, golden captures the updated golden contents for later // writing. type Golden struct { + id string data map[string][]byte updated map[string][]byte } @@ -382,7 +427,7 @@ func (g *Golden) Get(t testing.TB, name string, update func() []byte) []byte { // Multiple tests may reference the same golden data, but if they do they // must agree about its expected content. if diff := compare.Text(string(existing), string(d)); diff != "" { - t.Fatalf("conflicting updates for golden data %s:\n%s", name, diff) + t.Errorf("conflicting updates for golden data %s/%s:\n%s", g.id, name, diff) } } if g.updated == nil { @@ -425,10 +470,11 @@ func loadMarkerTests(dir string) ([]*MarkerTest, error) { func loadMarkerTest(name string, archive *txtar.Archive) (*MarkerTest, error) { test := &MarkerTest{ - name: name, - fset: token.NewFileSet(), - files: make(map[string][]byte), - golden: make(map[string]*Golden), + name: name, + fset: token.NewFileSet(), + archive: archive, + files: make(map[string][]byte), + golden: make(map[string]*Golden), } for _, file := range archive.Files { if file.Name == "flags" { @@ -438,8 +484,13 @@ func loadMarkerTest(name string, archive *txtar.Archive) (*MarkerTest, error) { } continue } - if strings.HasPrefix(file.Name, "@") { - // golden content + if file.Name == "settings.json" { + if err := json.Unmarshal(file.Data, &test.settings); err != nil { + return nil, err + } + continue + } + if strings.HasPrefix(file.Name, "@") { // golden content // TODO: use strings.Cut once we are on 1.18+. idx := strings.IndexByte(file.Name, '/') if idx < 0 { @@ -448,20 +499,23 @@ func loadMarkerTest(name string, archive *txtar.Archive) (*MarkerTest, error) { goldenID := file.Name[len("@"):idx] if _, ok := test.golden[goldenID]; !ok { test.golden[goldenID] = &Golden{ + id: goldenID, data: make(map[string][]byte), } } test.golden[goldenID].data[file.Name[idx+len("/"):]] = file.Data - } else { - // ordinary file content - notes, err := expect.Parse(test.fset, file.Name, file.Data) - if err != nil { - return nil, fmt.Errorf("parsing notes in %q: %v", file.Name, err) - } - test.notes = append(test.notes, notes...) - test.files[file.Name] = file.Data + continue + } + + // ordinary file content + notes, err := expect.Parse(test.fset, file.Name, file.Data) + if err != nil { + return nil, fmt.Errorf("parsing notes in %q: %v", file.Name, err) } + test.notes = append(test.notes, notes...) + test.files[file.Name] = file.Data } + return test, nil } @@ -471,25 +525,32 @@ func writeMarkerTests(dir string, tests []*MarkerTest) error { if !test.executed { continue } - arch := &txtar.Archive{} + arch := &txtar.Archive{ + Comment: test.archive.Comment, + } // Special configuration files go first. if len(test.flags) > 0 { flags := strings.Join(test.flags, " ") arch.Files = append(arch.Files, txtar.File{Name: "flags", Data: []byte(flags)}) } + if len(test.settings) > 0 { + data, err := json.MarshalIndent(test.settings, "", "\t") + if err != nil { + return err + } + arch.Files = append(arch.Files, txtar.File{Name: "settings.json", Data: data}) + } - // ...followed by ordinary files - var files []txtar.File - for name, data := range test.files { - files = append(files, txtar.File{Name: name, Data: data}) + // ...followed by ordinary files. Preserve the order they appeared in the + // original archive. + for _, file := range test.archive.Files { + if _, ok := test.files[file.Name]; ok { // ordinary file + arch.Files = append(arch.Files, file) + } } - sort.Slice(files, func(i, j int) bool { - return files[i].Name < files[j].Name - }) - arch.Files = append(arch.Files, files...) - // ...followed by golden files + // ...followed by golden files. var goldenFiles []txtar.File for id, golden := range test.golden { for name, data := range golden.updated { @@ -497,6 +558,8 @@ func writeMarkerTests(dir string, tests []*MarkerTest) error { goldenFiles = append(goldenFiles, txtar.File{Name: fullName, Data: data}) } } + // Unlike ordinary files, golden content is usually not manually edited, so + // we sort lexically. sort.Slice(goldenFiles, func(i, j int) bool { return goldenFiles[i].Name < goldenFiles[j].Name }) @@ -515,7 +578,7 @@ func writeMarkerTests(dir string, tests []*MarkerTest) error { // // TODO(rfindley): simplify and refactor the construction of testing // environments across regtests, marker tests, and benchmarks. -func newEnv(t *testing.T, cache *cache.Cache, files map[string][]byte) *Env { +func newEnv(t *testing.T, cache *cache.Cache, files map[string][]byte, settings map[string]interface{}) *Env { sandbox, err := fake.NewSandbox(&fake.SandboxConfig{ RootDir: t.TempDir(), GOPROXY: "https://proxy.golang.org", @@ -533,7 +596,10 @@ func newEnv(t *testing.T, cache *cache.Cache, files map[string][]byte) *Env { awaiter := NewAwaiter(sandbox.Workdir) ss := lsprpc.NewStreamServer(cache, false, hooks.Options) server := servertest.NewPipeServer(ss, jsonrpc2.NewRawStream) - editor, err := fake.NewEditor(sandbox, fake.EditorConfig{}).Connect(ctx, server, awaiter.Hooks()) + config := fake.EditorConfig{ + Settings: settings, + } + editor, err := fake.NewEditor(sandbox, config).Connect(ctx, server, awaiter.Hooks()) if err != nil { sandbox.Close() // ignore error t.Fatal(err) @@ -557,7 +623,59 @@ type markerInfo struct { converters []converter // to convert non-blank arguments } -type converter func(*Env, *MarkerTest, *expect.Note, interface{}) (interface{}, error) +type markerContext struct { + test *MarkerTest + env *Env + + // Collected information. + locations map[expect.Identifier]protocol.Location + diags map[protocol.Location][]protocol.Diagnostic +} + +// fmtLoc formats the given location in the context of the test, using +// archive-relative paths for files, and including the line number in the full +// archive file. +func (c markerContext) fmtLoc(loc protocol.Location) string { + if loc == (protocol.Location{}) { + return "" + } + lines := bytes.Count(c.test.archive.Comment, []byte("\n")) + var name string + for _, f := range c.test.archive.Files { + lines++ // -- separator -- + uri := c.env.Sandbox.Workdir.URI(f.Name) + if uri == loc.URI { + name = f.Name + break + } + lines += bytes.Count(f.Data, []byte("\n")) + } + if name == "" { + c.env.T.Errorf("unable to find %s in test archive", loc) + return "" + } + m := c.env.Editor.Mapper(name) + s, err := m.LocationSpan(loc) + if err != nil { + c.env.T.Errorf("error formatting location %s: %v", loc, err) + return "" + } + + innerSpan := fmt.Sprintf("%d:%d", s.Start().Line(), s.Start().Column()) // relative to the embedded file + outerSpan := fmt.Sprintf("%d:%d", lines+s.Start().Line(), s.Start().Column()) // relative to the archive file + if s.End().Line() == s.Start().Line() { + innerSpan += fmt.Sprintf("-%d", s.End().Column()) + outerSpan += fmt.Sprintf("-%d", s.End().Column()) + } else { + innerSpan += fmt.Sprintf("-%d:%d", s.End().Line(), s.End().Column()) + innerSpan += fmt.Sprintf("-%d:%d", lines+s.End().Line(), s.End().Column()) + } + + return fmt.Sprintf("%s:%s (%s:%s)", name, innerSpan, c.test.name, outerSpan) +} + +// converter is the signature of argument converters. +type converter func(*markerContext, *expect.Note, interface{}) (interface{}, error) // makeMarker uses reflection to load markerInfo for the given func value. func makeMarker(fn interface{}) markerInfo { @@ -565,8 +683,8 @@ func makeMarker(fn interface{}) markerInfo { fn: reflect.ValueOf(fn), } mtyp := mi.fn.Type() - if mtyp.NumIn() == 0 || mtyp.In(0) != envType { - panic(fmt.Sprintf("marker function %#v must accept *Env as its first argument", mi.fn)) + if mtyp.NumIn() == 0 || mtyp.In(0) != markerContextType { + panic(fmt.Sprintf("marker function %#v must accept markerContext as its first argument", mi.fn)) } if mtyp.NumOut() != 0 { panic(fmt.Sprintf("marker function %#v must not have results", mi.fn)) @@ -582,19 +700,20 @@ func makeMarker(fn interface{}) markerInfo { // Types with special conversions. var ( - envType = reflect.TypeOf(&Env{}) - locationType = reflect.TypeOf(protocol.Location{}) - goldenType = reflect.TypeOf(&Golden{}) + goldenType = reflect.TypeOf(&Golden{}) + locationType = reflect.TypeOf(protocol.Location{}) + markerContextType = reflect.TypeOf(&markerContext{}) + regexpType = reflect.TypeOf(®exp.Regexp{}) ) func makeConverter(paramType reflect.Type) converter { switch paramType { - case locationType: - return locationConverter case goldenType: return goldenConverter + case locationType: + return locationConverter default: - return func(_ *Env, _ *MarkerTest, _ *expect.Note, arg interface{}) (interface{}, error) { + return func(_ *markerContext, _ *expect.Note, arg interface{}) (interface{}, error) { if argType := reflect.TypeOf(arg); argType != paramType { return nil, fmt.Errorf("cannot convert type %s to %s", argType, paramType) } @@ -606,42 +725,85 @@ func makeConverter(paramType reflect.Type) converter { // locationConverter converts a string argument into the protocol location // corresponding to the first position of the string in the line preceding the // note. -func locationConverter(env *Env, test *MarkerTest, note *expect.Note, arg interface{}) (interface{}, error) { - file := test.fset.File(note.Pos) - posn := safetoken.StartPosition(test.fset, note.Pos) - lineStart := file.LineStart(posn.Line) - startOff, endOff, err := safetoken.Offsets(file, lineStart, note.Pos) +func locationConverter(c *markerContext, note *expect.Note, arg interface{}) (interface{}, error) { + switch arg := arg.(type) { + case string: + startOff, preceding, m, err := linePreceding(c, note.Pos) + if err != nil { + return protocol.Location{}, err + } + idx := bytes.Index(preceding, []byte(arg)) + if idx < 0 { + return nil, fmt.Errorf("substring %q not found in %q", arg, preceding) + } + off := startOff + idx + return m.OffsetLocation(off, off+len(arg)) + case *regexp.Regexp: + return findRegexpInLine(c, note.Pos, arg) + case expect.Identifier: + loc, ok := c.locations[arg] + if !ok { + return nil, fmt.Errorf("no location named %q", arg) + } + return loc, nil + default: + return nil, fmt.Errorf("cannot convert argument type %T to location (must be a string to match the preceding line)", arg) + } +} + +// findRegexpInLine searches the partial line preceding pos for a match for the +// regular expression re, returning a location spanning the first match. If re +// contains exactly one subgroup, the position of this subgroup match is +// returned rather than the position of the full match. +func findRegexpInLine(c *markerContext, pos token.Pos, re *regexp.Regexp) (protocol.Location, error) { + startOff, preceding, m, err := linePreceding(c, pos) if err != nil { - return nil, err + return protocol.Location{}, err } - m := env.Editor.Mapper(file.Name()) - substr, ok := arg.(string) - if !ok { - return nil, fmt.Errorf("cannot convert argument type %T to location (must be a string to match the preceding line)", arg) + + matches := re.FindSubmatchIndex(preceding) + if len(matches) == 0 { + return protocol.Location{}, fmt.Errorf("no match for regexp %q found in %q", re, string(preceding)) + } + var start, end int + switch len(matches) { + case 2: + // no subgroups: return the range of the regexp expression + start, end = matches[0], matches[1] + case 4: + // one subgroup: return its range + start, end = matches[2], matches[3] + default: + return protocol.Location{}, fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", re, len(matches)/2-1) } - preceding := m.Content[startOff:endOff] - idx := bytes.Index(preceding, []byte(substr)) - if idx < 0 { - return nil, fmt.Errorf("substring %q not found in %q", substr, preceding) + return m.OffsetLocation(start+startOff, end+startOff) +} + +func linePreceding(c *markerContext, pos token.Pos) (int, []byte, *protocol.Mapper, error) { + file := c.test.fset.File(pos) + posn := safetoken.Position(file, pos) + lineStart := file.LineStart(posn.Line) + startOff, endOff, err := safetoken.Offsets(file, lineStart, pos) + if err != nil { + return 0, nil, nil, err } - off := startOff + idx - loc, err := m.OffsetLocation(off, off+len(substr)) - return loc, err + m := c.env.Editor.Mapper(file.Name()) + return startOff, m.Content[startOff:endOff], m, nil } -// goldenConverter converts an identifier into the Golden directory of content +// goldenConverter convers an identifier into the Golden directory of content // prefixed by @ in the test archive file. -func goldenConverter(_ *Env, test *MarkerTest, note *expect.Note, arg interface{}) (interface{}, error) { +func goldenConverter(c *markerContext, note *expect.Note, arg interface{}) (interface{}, error) { switch arg := arg.(type) { case expect.Identifier: - golden := test.golden[string(arg)] + golden := c.test.golden[string(arg)] // If there was no golden content for this identifier, we must create one - // to handle the case where -update_golden is set: we need a place to store + // to handle the case where -update is set: we need a place to store // the updated content. if golden == nil { golden = new(Golden) - test.golden[string(arg)] = golden + c.test.golden[string(arg)] = golden } return golden, nil default: @@ -649,14 +811,26 @@ func goldenConverter(_ *Env, test *MarkerTest, note *expect.Note, arg interface{ } } +// defMarker implements the @godef marker, running textDocument/definition at +// the given src location and asserting that there is exactly one resulting +// location, matching dst. +// +// TODO(rfindley): support a variadic destination set. +func defMarker(c *markerContext, src, dst protocol.Location) { + got := c.env.GoToDefinition(src) + if got != dst { + c.env.T.Errorf("%s: definition location does not match:\n\tgot: %s\n\twant %s", c.fmtLoc(src), c.fmtLoc(got), c.fmtLoc(dst)) + } +} + // hoverMarker implements the @hover marker, running textDocument/hover at the // given src location and asserting that the resulting hover is over the dst // location (typically a span surrounding src), and that the markdown content // matches the golden content. -func hoverMarker(env *Env, src, dst protocol.Location, golden *Golden) { - content, gotDst := env.Hover(src) +func hoverMarker(c *markerContext, src, dst protocol.Location, golden *Golden) { + content, gotDst := c.env.Hover(src) if gotDst != dst { - env.T.Errorf("%s: hover location does not match:\n\tgot: %s\n\twant %s)", src, gotDst, dst) + c.env.T.Errorf("%s: hover location does not match:\n\tgot: %s\n\twant %s)", c.fmtLoc(src), c.fmtLoc(gotDst), c.fmtLoc(dst)) } gotMD := "" if content != nil { @@ -664,7 +838,7 @@ func hoverMarker(env *Env, src, dst protocol.Location, golden *Golden) { } wantMD := "" if golden != nil { - wantMD = string(golden.Get(env.T, "hover.md", func() []byte { return []byte(gotMD) })) + wantMD = string(golden.Get(c.env.T, "hover.md", func() []byte { return []byte(gotMD) })) } // Normalize newline termination: archive files can't express non-newline // terminated files. @@ -672,6 +846,30 @@ func hoverMarker(env *Env, src, dst protocol.Location, golden *Golden) { gotMD += "\n" } if diff := tests.DiffMarkdown(wantMD, gotMD); diff != "" { - env.T.Errorf("%s: hover markdown mismatch (-want +got):\n%s", src, diff) + c.env.T.Errorf("%s: hover markdown mismatch (-want +got):\n%s", c.fmtLoc(src), diff) + } +} + +// locMarker implements the @loc hover marker. It is executed before other +// markers, so that locations are available. +func locMarker(c *markerContext, name expect.Identifier, loc protocol.Location) { + c.locations[name] = loc +} + +// diagMarker implements the @diag hover marker. It eliminates diagnostics from +// the observed set in the markerContext. +func diagMarker(c *markerContext, loc protocol.Location, re *regexp.Regexp) { + idx := -1 + diags := c.diags[loc] + for i, diag := range diags { + if re.MatchString(diag.Message) { + idx = i + break + } + } + if idx >= 0 { + c.diags[loc] = append(diags[:idx], diags[idx+1:]...) + } else { + c.env.T.Errorf("%s: no diagnostic matches %q", c.fmtLoc(loc), re) } } diff --git a/gopls/internal/lsp/testdata/godef/a/a.go b/gopls/internal/lsp/testdata/godef/a/a.go deleted file mode 100644 index 53ca6ddc412..00000000000 --- a/gopls/internal/lsp/testdata/godef/a/a.go +++ /dev/null @@ -1,111 +0,0 @@ -// Package a is a package for testing go to definition. -package a //@mark(aPackage, "a "),hoverdef("a ", aPackage) - -import ( - "fmt" - "go/types" - "sync" -) - -var ( - // x is a variable. - x string //@x,hoverdef("x", x) -) - -// Constant block. When I hover on h, I should see this comment. -const ( - // When I hover on g, I should see this comment. - g = 1 //@g,hoverdef("g", g) - - h = 2 //@h,hoverdef("h", h) -) - -// z is a variable too. -var z string //@z,hoverdef("z", z) - -type A string //@mark(AString, "A") - -func AStuff() { //@AStuff - x := 5 - Random2(x) //@godef("dom2", Random2) - Random() //@godef("()", Random) - - var err error //@err - fmt.Printf("%v", err) //@godef("err", err) - - var y string //@string,hoverdef("string", string) - _ = make([]int, 0) //@make,hoverdef("make", make) - - var mu sync.Mutex - mu.Lock() //@Lock,hoverdef("Lock", Lock) - - var typ *types.Named //@mark(typesImport, "types"),hoverdef("types", typesImport) - typ.Obj().Name() //@Name,hoverdef("Name", Name) -} - -type A struct { -} - -func (_ A) Hi() {} //@mark(AHi, "Hi") - -type S struct { - Field int //@mark(AField, "Field") - R // embed a struct - H // embed an interface -} - -type R struct { - Field2 int //@mark(AField2, "Field2") -} - -func (_ R) Hey() {} //@mark(AHey, "Hey") - -type H interface { //@H - Goodbye() //@mark(AGoodbye, "Goodbye") -} - -type I interface { //@I - B() //@mark(AB, "B") - J -} - -type J interface { //@J - Hello() //@mark(AHello, "Hello") -} - -func _() { - // 1st type declaration block - type ( - a struct { //@mark(declBlockA, "a"),hoverdef("a", declBlockA) - x string - } - ) - - // 2nd type declaration block - type ( - // b has a comment - b struct{} //@mark(declBlockB, "b"),hoverdef("b", declBlockB) - ) - - // 3rd type declaration block - type ( - // c is a struct - c struct { //@mark(declBlockC, "c"),hoverdef("c", declBlockC) - f string - } - - d string //@mark(declBlockD, "d"),hoverdef("d", declBlockD) - ) - - type ( - e struct { //@mark(declBlockE, "e"),hoverdef("e", declBlockE) - f float64 - } // e has a comment - ) -} - -var ( - hh H //@hoverdef("H", H) - ii I //@hoverdef("I", I) - jj J //@hoverdef("J", J) -) diff --git a/gopls/internal/lsp/testdata/godef/a/a.go.golden b/gopls/internal/lsp/testdata/godef/a/a.go.golden deleted file mode 100644 index 470396d068c..00000000000 --- a/gopls/internal/lsp/testdata/godef/a/a.go.golden +++ /dev/null @@ -1,230 +0,0 @@ --- H-hoverdef -- -```go -type H interface { - Goodbye() //@mark(AGoodbye, "Goodbye") -} -``` - -[`a.H` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#H) --- I-hoverdef -- -```go -type I interface { - B() //@mark(AB, "B") - J -} -``` - -[`a.I` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#I) --- J-hoverdef -- -```go -type J interface { - Hello() //@mark(AHello, "Hello") -} -``` - -[`a.J` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#J) --- Lock-hoverdef -- -```go -func (*sync.Mutex).Lock() -``` - -Lock locks m. - - -[`(sync.Mutex).Lock` on pkg.go.dev](https://pkg.go.dev/sync#Mutex.Lock) --- Name-hoverdef -- -```go -func (*types.object).Name() string -``` - -Name returns the object's (package-local, unqualified) name. - - -[`(types.TypeName).Name` on pkg.go.dev](https://pkg.go.dev/go/types#TypeName.Name) --- Random-definition -- -godef/a/random.go:3:6-12: defined here as ```go -func Random() int -``` - -[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Random) --- 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": "```go\nfunc Random() int\n```\n\n[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Random)" -} - --- Random-hoverdef -- -```go -func Random() int -``` - -[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Random) --- Random2-definition -- -godef/a/random.go:8:6-13: defined here as ```go -func Random2(y int) int -``` - -[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Random2) --- 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": "```go\nfunc Random2(y int) int\n```\n\n[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Random2)" -} - --- Random2-hoverdef -- -```go -func Random2(y int) int -``` - -[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Random2) --- aPackage-hoverdef -- -Package a is a package for testing go to definition. - --- declBlockA-hoverdef -- -```go -type a struct { - x string -} -``` - -1st type declaration block - --- declBlockB-hoverdef -- -```go -type b struct{} -``` - -b has a comment - --- declBlockC-hoverdef -- -```go -type c struct { - f string -} -``` - -c is a struct - --- declBlockD-hoverdef -- -```go -type d string -``` - -3rd type declaration block - --- declBlockE-hoverdef -- -```go -type e struct { - f float64 -} -``` - -e has a comment - --- err-definition -- -godef/a/a.go:33:6-9: defined here as ```go -var err error -``` - -@err --- err-definition-json -- -{ - "span": { - "uri": "file://godef/a/a.go", - "start": { - "line": 33, - "column": 6, - "offset": 612 - }, - "end": { - "line": 33, - "column": 9, - "offset": 615 - } - }, - "description": "```go\nvar err error\n```\n\n@err" -} - --- err-hoverdef -- -```go -var err error -``` - -@err - --- g-hoverdef -- -```go -const g untyped int = 1 -``` - -When I hover on g, I should see this comment. - --- h-hoverdef -- -```go -const h untyped int = 2 -``` - -Constant block. - --- make-hoverdef -- -```go -func make(t Type, size ...int) Type -``` - -The make built-in function allocates and initializes an object of type slice, map, or chan (only). - - -[`make` on pkg.go.dev](https://pkg.go.dev/builtin#make) --- string-hoverdef -- -```go -type string string -``` - -string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. - - -[`string` on pkg.go.dev](https://pkg.go.dev/builtin#string) --- typesImport-hoverdef -- -```go -package types ("go/types") -``` - -[`types` on pkg.go.dev](https://pkg.go.dev/go/types) --- x-hoverdef -- -```go -var x string -``` - -x is a variable. - --- z-hoverdef -- -```go -var z string -``` - -z is a variable too. - diff --git a/gopls/internal/lsp/testdata/godef/a/a_test.go b/gopls/internal/lsp/testdata/godef/a/a_test.go deleted file mode 100644 index 77bd633b6c0..00000000000 --- a/gopls/internal/lsp/testdata/godef/a/a_test.go +++ /dev/null @@ -1,8 +0,0 @@ -package a - -import ( - "testing" -) - -func TestA(t *testing.T) { //@TestA,godef(TestA, TestA) -} diff --git a/gopls/internal/lsp/testdata/godef/a/a_test.go.golden b/gopls/internal/lsp/testdata/godef/a/a_test.go.golden deleted file mode 100644 index e5cb3d799cc..00000000000 --- a/gopls/internal/lsp/testdata/godef/a/a_test.go.golden +++ /dev/null @@ -1,26 +0,0 @@ --- TestA-definition -- -godef/a/a_test.go:7:6-11: defined here as ```go -func TestA(t *testing.T) -``` --- TestA-definition-json -- -{ - "span": { - "uri": "file://godef/a/a_test.go", - "start": { - "line": 7, - "column": 6, - "offset": 39 - }, - "end": { - "line": 7, - "column": 11, - "offset": 44 - } - }, - "description": "```go\nfunc TestA(t *testing.T)\n```" -} - --- TestA-hoverdef -- -```go -func TestA(t *testing.T) -``` diff --git a/gopls/internal/lsp/testdata/godef/a/f.go b/gopls/internal/lsp/testdata/godef/a/f.go index 589c45fc1ae..10f88262a81 100644 --- a/gopls/internal/lsp/testdata/godef/a/f.go +++ b/gopls/internal/lsp/testdata/godef/a/f.go @@ -1,3 +1,4 @@ +// Package a is a package for testing go to definition. package a import "fmt" diff --git a/gopls/internal/lsp/testdata/godef/a/random.go b/gopls/internal/lsp/testdata/godef/a/random.go deleted file mode 100644 index 62055c1fcec..00000000000 --- a/gopls/internal/lsp/testdata/godef/a/random.go +++ /dev/null @@ -1,31 +0,0 @@ -package a - -func Random() int { //@Random - y := 6 + 7 - return y -} - -func Random2(y int) int { //@Random2,mark(RandomParamY, "y") - return y //@godef("y", RandomParamY) -} - -type Pos struct { - x, y int //@mark(PosX, "x"),mark(PosY, "y") -} - -// Typ has a comment. Its fields do not. -type Typ struct{ field string } //@mark(TypField, "field") - -func _() { - x := &Typ{} - x.field //@godef("field", TypField) -} - -func (p *Pos) Sum() int { //@mark(PosSum, "Sum") - return p.x + p.y //@godef("x", PosX) -} - -func _() { - var p Pos - _ = p.Sum() //@godef("()", PosSum) -} diff --git a/gopls/internal/lsp/testdata/godef/a/random.go.golden b/gopls/internal/lsp/testdata/godef/a/random.go.golden deleted file mode 100644 index d7ba51d1e82..00000000000 --- a/gopls/internal/lsp/testdata/godef/a/random.go.golden +++ /dev/null @@ -1,113 +0,0 @@ --- PosSum-definition -- -godef/a/random.go:24:15-18: defined here as ```go -func (*Pos).Sum() int -``` - -[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Pos.Sum) --- PosSum-definition-json -- -{ - "span": { - "uri": "file://godef/a/random.go", - "start": { - "line": 24, - "column": 15, - "offset": 413 - }, - "end": { - "line": 24, - "column": 18, - "offset": 416 - } - }, - "description": "```go\nfunc (*Pos).Sum() int\n```\n\n[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Pos.Sum)" -} - --- PosSum-hoverdef -- -```go -func (*Pos).Sum() int -``` - -[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#Pos.Sum) --- PosX-definition -- -godef/a/random.go:13:2-3: defined here as ```go -field x int -``` - -@mark(PosX, "x"),mark(PosY, "y") --- 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": "```go\nfield x int\n```\n\n@mark(PosX, \"x\"),mark(PosY, \"y\")" -} - --- PosX-hoverdef -- -```go -field x int -``` - -@mark(PosX, "x"),mark(PosY, "y") - --- RandomParamY-definition -- -godef/a/random.go:8:14-15: defined here as ```go -var y int -``` --- 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": "```go\nvar y int\n```" -} - --- RandomParamY-hoverdef -- -```go -var y int -``` --- TypField-definition -- -godef/a/random.go:17:18-23: defined here as ```go -field field string -``` --- TypField-definition-json -- -{ - "span": { - "uri": "file://godef/a/random.go", - "start": { - "line": 17, - "column": 18, - "offset": 292 - }, - "end": { - "line": 17, - "column": 23, - "offset": 297 - } - }, - "description": "```go\nfield field string\n```" -} - --- TypField-hoverdef -- -```go -field field string -``` diff --git a/gopls/internal/lsp/testdata/godef/b/b.go b/gopls/internal/lsp/testdata/godef/b/b.go deleted file mode 100644 index ee536ecfdc3..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/b.go +++ /dev/null @@ -1,57 +0,0 @@ -package b - -import ( - myFoo "golang.org/lsptests/foo" //@mark(myFoo, "myFoo"),godef("myFoo", myFoo) - "golang.org/lsptests/godef/a" //@mark(AImport, re"\".*\"") -) - -type Embed struct { - *a.A - a.I - a.S -} - -func _() { - e := Embed{} - e.Hi() //@hoverdef("Hi", AHi) - e.B() //@hoverdef("B", AB) - e.Field //@hoverdef("Field", AField) - e.Field2 //@hoverdef("Field2", AField2) - e.Hello() //@hoverdef("Hello", AHello) - e.Hey() //@hoverdef("Hey", AHey) - e.Goodbye() //@hoverdef("Goodbye", AGoodbye) -} - -type aAlias = a.A //@mark(aAlias, "aAlias") - -type S1 struct { //@S1 - F1 int //@mark(S1F1, "F1") - S2 //@godef("S2", S2),mark(S1S2, "S2") - a.A //@godef("A", AString) - aAlias //@godef("a", aAlias) -} - -type S2 struct { //@S2 - F1 string //@mark(S2F1, "F1") - F2 int //@mark(S2F2, "F2") - *a.A //@godef("A", AString),godef("a",AImport) -} - -type S3 struct { - F1 struct { - a.A //@godef("A", AString) - } -} - -func Bar() { - a.AStuff() //@godef("AStuff", AStuff) - var x S1 //@godef("S1", S1) - _ = x.S2 //@godef("S2", S1S2) - _ = x.F1 //@godef("F1", S1F1) - _ = x.F2 //@godef("F2", S2F2) - _ = x.S2.F1 //@godef("F1", S2F1) - - var _ *myFoo.StructFoo //@godef("myFoo", myFoo) -} - -const X = 0 //@mark(bX, "X"),godef("X", bX) diff --git a/gopls/internal/lsp/testdata/godef/b/b.go.golden b/gopls/internal/lsp/testdata/godef/b/b.go.golden deleted file mode 100644 index cfe3917ba88..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/b.go.golden +++ /dev/null @@ -1,480 +0,0 @@ --- AB-hoverdef -- -```go -func (a.I).B() -``` - -@mark(AB, "B") - - -[`(a.I).B` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#I.B) --- AField-hoverdef -- -```go -field Field int -``` - -@mark(AField, "Field") - - -[`(a.S).Field` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#S.Field) --- AField2-hoverdef -- -```go -field Field2 int -``` - -@mark(AField2, "Field2") - - -[`(a.R).Field2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#R.Field2) --- AGoodbye-hoverdef -- -```go -func (a.H).Goodbye() -``` - -@mark(AGoodbye, "Goodbye") - - -[`(a.H).Goodbye` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#H.Goodbye) --- AHello-hoverdef -- -```go -func (a.J).Hello() -``` - -@mark(AHello, "Hello") - - -[`(a.J).Hello` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#J.Hello) --- AHey-hoverdef -- -```go -func (a.R).Hey() -``` - -[`(a.R).Hey` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#R.Hey) --- AHi-hoverdef -- -```go -func (a.A).Hi() -``` - -[`(a.A).Hi` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#A.Hi) --- AImport-definition -- -godef/b/b.go:5:2-31: defined here as ```go -package a ("golang.org/lsptests/godef/a") -``` - -[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a) --- AImport-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 5, - "column": 2, - "offset": 100 - }, - "end": { - "line": 5, - "column": 31, - "offset": 129 - } - }, - "description": "```go\npackage a (\"golang.org/lsptests/godef/a\")\n```\n\n[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a)" -} - --- AImport-hoverdef -- -```go -package a ("golang.org/lsptests/godef/a") -``` - -[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a) --- AString-definition -- -godef/a/a.go:26:6-7: defined here as ```go -type A string - -func (a.A).Hi() -``` - -@mark(AString, "A") - - -[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#A) --- AString-definition-json -- -{ - "span": { - "uri": "file://godef/a/a.go", - "start": { - "line": 26, - "column": 6, - "offset": 467 - }, - "end": { - "line": 26, - "column": 7, - "offset": 468 - } - }, - "description": "```go\ntype A string\n\nfunc (a.A).Hi()\n```\n\n@mark(AString, \"A\")\n\n\n[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#A)" -} - --- AString-hoverdef -- -```go -type A string - -func (a.A).Hi() -``` - -@mark(AString, "A") - - -[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#A) --- AStuff-definition -- -godef/a/a.go:28:6-12: defined here as ```go -func a.AStuff() -``` - -[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#AStuff) --- AStuff-definition-json -- -{ - "span": { - "uri": "file://godef/a/a.go", - "start": { - "line": 28, - "column": 6, - "offset": 504 - }, - "end": { - "line": 28, - "column": 12, - "offset": 510 - } - }, - "description": "```go\nfunc a.AStuff()\n```\n\n[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#AStuff)" -} - --- AStuff-hoverdef -- -```go -func a.AStuff() -``` - -[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#AStuff) --- S1-definition -- -godef/b/b.go:27:6-8: defined here as ```go -type S1 struct { - F1 int //@mark(S1F1, "F1") - S2 //@godef("S2", S2),mark(S1S2, "S2") - a.A //@godef("A", AString) - aAlias //@godef("a", aAlias) -} -``` - -[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1) --- S1-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 27, - "column": 6, - "offset": 563 - }, - "end": { - "line": 27, - "column": 8, - "offset": 565 - } - }, - "description": "```go\ntype S1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n\taAlias //@godef(\"a\", aAlias)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1)" -} - --- S1-hoverdef -- -```go -type S1 struct { - F1 int //@mark(S1F1, "F1") - S2 //@godef("S2", S2),mark(S1S2, "S2") - a.A //@godef("A", AString) - aAlias //@godef("a", aAlias) -} -``` - -[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1) --- S1F1-definition -- -godef/b/b.go:28:2-4: defined here as ```go -field F1 int -``` - -@mark(S1F1, "F1") - - -[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.F1) --- S1F1-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 28, - "column": 2, - "offset": 582 - }, - "end": { - "line": 28, - "column": 4, - "offset": 584 - } - }, - "description": "```go\nfield F1 int\n```\n\n@mark(S1F1, \"F1\")\n\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.F1)" -} - --- S1F1-hoverdef -- -```go -field F1 int -``` - -@mark(S1F1, "F1") - - -[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.F1) --- S1S2-definition -- -godef/b/b.go:29:2-4: defined here as ```go -field S2 S2 -``` - -@godef("S2", S2),mark(S1S2, "S2") - - -[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.S2) --- S1S2-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 29, - "column": 2, - "offset": 614 - }, - "end": { - "line": 29, - "column": 4, - "offset": 616 - } - }, - "description": "```go\nfield S2 S2\n```\n\n@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.S2)" -} - --- S1S2-hoverdef -- -```go -field S2 S2 -``` - -@godef("S2", S2),mark(S1S2, "S2") - - -[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.S2) --- S2-definition -- -godef/b/b.go:34:6-8: defined here as ```go -type S2 struct { - F1 string //@mark(S2F1, "F1") - F2 int //@mark(S2F2, "F2") - *a.A //@godef("A", AString),godef("a",AImport) -} -``` - -[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2) --- S2-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 34, - "column": 6, - "offset": 738 - }, - "end": { - "line": 34, - "column": 8, - "offset": 740 - } - }, - "description": "```go\ntype S2 struct {\n\tF1 string //@mark(S2F1, \"F1\")\n\tF2 int //@mark(S2F2, \"F2\")\n\t*a.A //@godef(\"A\", AString),godef(\"a\",AImport)\n}\n```\n\n[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2)" -} - --- S2-hoverdef -- -```go -type S2 struct { - F1 string //@mark(S2F1, "F1") - F2 int //@mark(S2F2, "F2") - *a.A //@godef("A", AString),godef("a",AImport) -} -``` - -[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2) --- S2F1-definition -- -godef/b/b.go:35:2-4: defined here as ```go -field F1 string -``` - -@mark(S2F1, "F1") - - -[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2.F1) --- S2F1-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 35, - "column": 2, - "offset": 757 - }, - "end": { - "line": 35, - "column": 4, - "offset": 759 - } - }, - "description": "```go\nfield F1 string\n```\n\n@mark(S2F1, \"F1\")\n\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2.F1)" -} - --- S2F1-hoverdef -- -```go -field F1 string -``` - -@mark(S2F1, "F1") - - -[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2.F1) --- S2F2-definition -- -godef/b/b.go:36:2-4: defined here as ```go -field F2 int -``` - -@mark(S2F2, "F2") - - -[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2.F2) --- S2F2-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 36, - "column": 2, - "offset": 790 - }, - "end": { - "line": 36, - "column": 4, - "offset": 792 - } - }, - "description": "```go\nfield F2 int\n```\n\n@mark(S2F2, \"F2\")\n\n\n[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2.F2)" -} - --- S2F2-hoverdef -- -```go -field F2 int -``` - -@mark(S2F2, "F2") - - -[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S2.F2) --- aAlias-definition -- -godef/b/b.go:25:6-12: defined here as ```go -type aAlias = a.A - -func (a.A).Hi() -``` - -@mark(aAlias, "aAlias") --- aAlias-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 25, - "column": 6, - "offset": 518 - }, - "end": { - "line": 25, - "column": 12, - "offset": 524 - } - }, - "description": "```go\ntype aAlias = a.A\n\nfunc (a.A).Hi()\n```\n\n@mark(aAlias, \"aAlias\")" -} - --- aAlias-hoverdef -- -```go -type aAlias = a.A - -func (a.A).Hi() -``` - -@mark(aAlias, "aAlias") - --- bX-definition -- -godef/b/b.go:57:7-8: defined here as ```go -const X untyped int = 0 -``` - -@mark(bX, "X"),godef("X", bX) - - -[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#X) --- bX-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 57, - "column": 7, - "offset": 1225 - }, - "end": { - "line": 57, - "column": 8, - "offset": 1226 - } - }, - "description": "```go\nconst X untyped int = 0\n```\n\n@mark(bX, \"X\"),godef(\"X\", bX)\n\n\n[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#X)" -} - --- bX-hoverdef -- -```go -const X untyped int = 0 -``` - -@mark(bX, "X"),godef("X", bX) - - -[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#X) --- myFoo-definition -- -godef/b/b.go:4:2-7: defined here as ```go -package myFoo ("golang.org/lsptests/foo") -``` - -[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/foo) --- myFoo-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 4, - "column": 2, - "offset": 21 - }, - "end": { - "line": 4, - "column": 7, - "offset": 26 - } - }, - "description": "```go\npackage myFoo (\"golang.org/lsptests/foo\")\n```\n\n[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/foo)" -} - --- myFoo-hoverdef -- -```go -package myFoo ("golang.org/lsptests/foo") -``` - -[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/foo) diff --git a/gopls/internal/lsp/testdata/godef/b/c.go b/gopls/internal/lsp/testdata/godef/b/c.go deleted file mode 100644 index c8daf62422a..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/c.go +++ /dev/null @@ -1,8 +0,0 @@ -package b - -// This is the in-editor version of the file. -// The on-disk version is in c.go.saved. - -var _ = S1{ //@godef("S1", S1) - F1: 99, //@godef("F1", S1F1) -} diff --git a/gopls/internal/lsp/testdata/godef/b/c.go.golden b/gopls/internal/lsp/testdata/godef/b/c.go.golden deleted file mode 100644 index 575bd1e7b51..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/c.go.golden +++ /dev/null @@ -1,76 +0,0 @@ --- S1-definition -- -godef/b/b.go:27:6-8: defined here as ```go -type S1 struct { - F1 int //@mark(S1F1, "F1") - S2 //@godef("S2", S2),mark(S1S2, "S2") - a.A //@godef("A", AString) - aAlias //@godef("a", aAlias) -} -``` - -[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1) --- S1-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 27, - "column": 6, - "offset": 563 - }, - "end": { - "line": 27, - "column": 8, - "offset": 565 - } - }, - "description": "```go\ntype S1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n\taAlias //@godef(\"a\", aAlias)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1)" -} - --- S1-hoverdef -- -```go -type S1 struct { - F1 int //@mark(S1F1, "F1") - S2 //@godef("S2", S2),mark(S1S2, "S2") - a.A //@godef("A", AString) - aAlias //@godef("a", aAlias) -} -``` - -[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1) --- S1F1-definition -- -godef/b/b.go:28:2-4: defined here as ```go -field F1 int -``` - -@mark(S1F1, "F1") - - -[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.F1) --- S1F1-definition-json -- -{ - "span": { - "uri": "file://godef/b/b.go", - "start": { - "line": 28, - "column": 2, - "offset": 582 - }, - "end": { - "line": 28, - "column": 4, - "offset": 584 - } - }, - "description": "```go\nfield F1 int\n```\n\n@mark(S1F1, \"F1\")\n\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.F1)" -} - --- S1F1-hoverdef -- -```go -field F1 int -``` - -@mark(S1F1, "F1") - - -[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/b#S1.F1) diff --git a/gopls/internal/lsp/testdata/godef/b/c.go.saved b/gopls/internal/lsp/testdata/godef/b/c.go.saved deleted file mode 100644 index ff1a8794b48..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/c.go.saved +++ /dev/null @@ -1,7 +0,0 @@ -package b - -// This is the on-disk version of c.go, which represents -// the in-editor version of the file. - -} - diff --git a/gopls/internal/lsp/testdata/godef/b/h.go b/gopls/internal/lsp/testdata/godef/b/h.go deleted file mode 100644 index 88017643336..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/h.go +++ /dev/null @@ -1,10 +0,0 @@ -package b - -import . "golang.org/lsptests/godef/a" - -func _() { - // variable of type a.A - var _ A //@mark(AVariable, "_"),hoverdef("_", AVariable) - - AStuff() //@hoverdef("AStuff", AStuff) -} diff --git a/gopls/internal/lsp/testdata/godef/b/h.go.golden b/gopls/internal/lsp/testdata/godef/b/h.go.golden deleted file mode 100644 index 04c7a291338..00000000000 --- a/gopls/internal/lsp/testdata/godef/b/h.go.golden +++ /dev/null @@ -1,13 +0,0 @@ --- AStuff-hoverdef -- -```go -func AStuff() -``` - -[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/godef/a#AStuff) --- AVariable-hoverdef -- -```go -var _ A -``` - -variable of type a.A - diff --git a/gopls/internal/lsp/testdata/summary.txt.golden b/gopls/internal/lsp/testdata/summary.txt.golden index 478b6a6ca92..985361ba710 100644 --- a/gopls/internal/lsp/testdata/summary.txt.golden +++ b/gopls/internal/lsp/testdata/summary.txt.golden @@ -16,7 +16,7 @@ SemanticTokenCount = 3 SuggestedFixCount = 65 FunctionExtractionCount = 27 MethodExtractionCount = 6 -DefinitionsCount = 99 +DefinitionsCount = 47 TypeDefinitionsCount = 18 HighlightsCount = 69 InlayHintsCount = 4 diff --git a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden index 9d62f8b16f1..9ae4d13649d 100644 --- a/gopls/internal/lsp/testdata/summary_go1.18.txt.golden +++ b/gopls/internal/lsp/testdata/summary_go1.18.txt.golden @@ -16,7 +16,7 @@ SemanticTokenCount = 3 SuggestedFixCount = 71 FunctionExtractionCount = 27 MethodExtractionCount = 6 -DefinitionsCount = 99 +DefinitionsCount = 47 TypeDefinitionsCount = 18 HighlightsCount = 69 InlayHintsCount = 5 diff --git a/gopls/internal/regtest/marker/testdata/definition/embed.txt b/gopls/internal/regtest/marker/testdata/definition/embed.txt new file mode 100644 index 00000000000..b1131d86362 --- /dev/null +++ b/gopls/internal/regtest/marker/testdata/definition/embed.txt @@ -0,0 +1,226 @@ +This test checks definition and hover operations over embedded fields and methods. +-- go.mod -- +module mod.com + +go 1.18 +-- a/a.go -- +package a + +type A string //@loc(AString, "A") + +func (_ A) Hi() {} //@loc(AHi, "Hi") + +type S struct { + Field int //@loc(SField, "Field") + R // embed a struct + H // embed an interface +} + +type R struct { + Field2 int //@loc(RField2, "Field2") +} + +func (_ R) Hey() {} //@loc(RHey, "Hey") + +type H interface { //@loc(H, "H") + Goodbye() //@loc(HGoodbye, "Goodbye") +} + +type I interface { //@loc(I, "I") + B() //@loc(IB, "B") + J +} + +type J interface { //@loc(J, "J") + Hello() //@loc(JHello, "Hello") +} + +-- b/b.go -- +package b + +import "mod.com/a" //@loc(AImport, re"\".*\"") + +type Embed struct { + *a.A + a.I + a.S +} + +func _() { + e := Embed{} + e.Hi() //@def("Hi", AHi),hover("Hi", "Hi", AHi) + e.B() //@def("B", IB),hover("B", "B", IB) + _ = e.Field //@def("Field", SField),hover("Field", "Field", SField) + _ = e.Field2 //@def("Field2", RField2),hover("Field2", "Field2", RField2) + e.Hello() //@def("Hello", JHello),hover("Hello", "Hello",JHello) + e.Hey() //@def("Hey", RHey), hover("Hey", "Hey", RHey) + e.Goodbye() //@def("Goodbye", HGoodbye), hover("Goodbye", "Goodbye", HGoodbye) +} + +type aAlias = a.A //@loc(aAlias, "aAlias") + +type S1 struct { //@loc(S1, "S1") + F1 int //@loc(S1F1, "F1") + S2 //@loc(S1S2, "S2"),def("S2", S2),hover("S2", "S2", S2) + a.A //@def("A", AString),hover("A", "A", aA) + aAlias //@def("a", aAlias),hover("a", "aAlias", aAlias) +} + +type S2 struct { //@loc(S2, "S2") + F1 string //@loc(S2F1, "F1") + F2 int //@loc(S2F2, "F2") + *a.A //@def("A", AString),def("a",AImport) +} + +type S3 struct { + F1 struct { + a.A //@def("A", AString) + } +} + +func Bar() { + var x S1 //@def("S1", S1),hover("S1", "S1", S1) + _ = x.S2 //@def("S2", S1S2),hover("S2", "S2", S1S2) + _ = x.F1 //@def("F1", S1F1),hover("F1", "F1", S1F1) + _ = x.F2 //@def("F2", S2F2),hover("F2", "F2", S2F2) + _ = x.S2.F1 //@def("F1", S2F1),hover("F1", "F1", S2F1) +} +-- b/c.go -- +package b + +var _ = S1{ //@def("S1", S1),hover("S1", "S1", S1) + F1: 99, //@def("F1", S1F1),hover("F1", "F1", S1F1) +} +-- @AHi/hover.md -- +```go +func (a.A).Hi() +``` + +[`(a.A).Hi` on pkg.go.dev](https://pkg.go.dev/mod.com/a#A.Hi) +-- @HGoodbye/hover.md -- +```go +func (a.H).Goodbye() +``` + +@loc(HGoodbye, "Goodbye") + + +[`(a.H).Goodbye` on pkg.go.dev](https://pkg.go.dev/mod.com/a#H.Goodbye) +-- @IB/hover.md -- +```go +func (a.I).B() +``` + +@loc(IB, "B") + + +[`(a.I).B` on pkg.go.dev](https://pkg.go.dev/mod.com/a#I.B) +-- @JHello/hover.md -- +```go +func (a.J).Hello() +``` + +@loc(JHello, "Hello") + + +[`(a.J).Hello` on pkg.go.dev](https://pkg.go.dev/mod.com/a#J.Hello) +-- @RField2/hover.md -- +```go +field Field2 int +``` + +@loc(RField2, "Field2") + + +[`(a.R).Field2` on pkg.go.dev](https://pkg.go.dev/mod.com/a#R.Field2) +-- @RHey/hover.md -- +```go +func (a.R).Hey() +``` + +[`(a.R).Hey` on pkg.go.dev](https://pkg.go.dev/mod.com/a#R.Hey) +-- @S1/hover.md -- +```go +type S1 struct { + F1 int //@loc(S1F1, "F1") + S2 //@loc(S1S2, "S2"),def("S2", S2),hover("S2", "S2", S2) + a.A //@def("A", AString),hover("A", "A", aA) + aAlias //@def("a", aAlias),hover("a", "aAlias", aAlias) +} +``` + +[`b.S1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1) +-- @S1F1/hover.md -- +```go +field F1 int +``` + +@loc(S1F1, "F1") + + +[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1.F1) +-- @S1S2/hover.md -- +```go +field S2 S2 +``` + +@loc(S1S2, "S2"),def("S2", S2),hover("S2", "S2", S2) + + +[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1.S2) +-- @S2/hover.md -- +```go +type S2 struct { + F1 string //@loc(S2F1, "F1") + F2 int //@loc(S2F2, "F2") + *a.A //@def("A", AString),def("a",AImport) +} +``` + +[`b.S2` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S2) +-- @S2F1/hover.md -- +```go +field F1 string +``` + +@loc(S2F1, "F1") + + +[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S2.F1) +-- @S2F2/hover.md -- +```go +field F2 int +``` + +@loc(S2F2, "F2") + + +[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S2.F2) +-- @SField/hover.md -- +```go +field Field int +``` + +@loc(SField, "Field") + + +[`(a.S).Field` on pkg.go.dev](https://pkg.go.dev/mod.com/a#S.Field) +-- @aA/hover.md -- +```go +type A string + +func (a.A).Hi() +``` + +@loc(AString, "A") + + +[`a.A` on pkg.go.dev](https://pkg.go.dev/mod.com/a#A) +-- @aAlias/hover.md -- +```go +type aAlias = a.A + +func (a.A).Hi() +``` + +@loc(aAlias, "aAlias") diff --git a/gopls/internal/regtest/marker/testdata/definition/import.txt b/gopls/internal/regtest/marker/testdata/definition/import.txt new file mode 100644 index 00000000000..9e5e5929aa9 --- /dev/null +++ b/gopls/internal/regtest/marker/testdata/definition/import.txt @@ -0,0 +1,52 @@ +This test checks definition and hover over imports. +-- go.mod -- +module mod.com + +go 1.18 +-- foo/foo.go -- +package foo + +type Foo struct{} + +// DoFoo does foo. +func DoFoo() {} //@loc(DoFoo, "DoFoo") +-- bar/bar.go -- +package bar + +import ( + myFoo "mod.com/foo" //@loc(myFoo, "myFoo") +) + +var _ *myFoo.Foo //@def("myFoo", myFoo),hover("myFoo", "myFoo", myFoo) +-- bar/dotimport.go -- +package bar + +import . "mod.com/foo" + +func _() { + // variable of type foo.Foo + var _ Foo //@hover("_", "_", FooVar) + + DoFoo() //@hover("DoFoo", "DoFoo", DoFoo) +} +-- @DoFoo/hover.md -- +```go +func DoFoo() +``` + +DoFoo does foo. + + +[`foo.DoFoo` on pkg.go.dev](https://pkg.go.dev/mod.com/foo#DoFoo) +-- @FooVar/hover.md -- +```go +var _ Foo +``` + +variable of type foo.Foo +-- @myFoo/hover.md -- +```go +package myFoo ("mod.com/foo") +``` + +[`myFoo` on pkg.go.dev](https://pkg.go.dev/mod.com/foo) diff --git a/gopls/internal/regtest/marker/testdata/definition/misc.txt b/gopls/internal/regtest/marker/testdata/definition/misc.txt new file mode 100644 index 00000000000..48f5d340c43 --- /dev/null +++ b/gopls/internal/regtest/marker/testdata/definition/misc.txt @@ -0,0 +1,230 @@ +This test exercises miscellaneous definition and hover requests. +-- go.mod -- +module mod.com + +go 1.16 +-- a.go -- +package a //@loc(aPackage, re"package (a)"),hover(aPackage, aPackage, aPackage) + +var ( + // x is a variable. + x string //@loc(x, "x"),hover(x, x, hoverx) +) + +// Constant block. When I hover on h, I should see this comment. +const ( + // When I hover on g, I should see this comment. + g = 1 //@hover("g", "g", hoverg) + + h = 2 //@hover("h", "h", hoverh) +) + +// z is a variable too. +var z string //@loc(z, "z"),hover(z, z, hoverz) + +func AStuff() { //@loc(AStuff, "AStuff") + x := 5 + Random2(x) //@def("dom2", Random2) + Random() //@def("()", Random) +} + +type H interface { //@loc(H, "H") + Goodbye() +} + +type I interface { //@loc(I, "I") + B() + J +} + +type J interface { //@loc(J, "J") + Hello() +} + +func _() { + // 1st type declaration block + type ( + a struct { //@hover("a", "a", hoverDeclBlocka) + x string + } + ) + + // 2nd type declaration block + type ( + // b has a comment + b struct{} //@hover("b", "b", hoverDeclBlockb) + ) + + // 3rd type declaration block + type ( + // c is a struct + c struct { //@hover("c", "c", hoverDeclBlockc) + f string + } + + d string //@hover("d", "d", hoverDeclBlockd) + ) + + type ( + e struct { //@hover("e", "e", hoverDeclBlocke) + f float64 + } // e has a comment + ) +} + +var ( + hh H //@hover("H", "H", hoverH) + ii I //@hover("I", "I", hoverI) + jj J //@hover("J", "J", hoverJ) +) +-- a_test.go -- +package a + +import ( + "testing" +) + +func TestA(t *testing.T) { //@hover("TestA", "TestA", hoverTestA) +} +-- random.go -- +package a + +func Random() int { //@loc(Random, "Random") + y := 6 + 7 + return y +} + +func Random2(y int) int { //@loc(Random2, "Random2"),loc(RandomParamY, "y") + return y //@def("y", RandomParamY),hover("y", "y", hovery) +} + +type Pos struct { + x, y int //@loc(PosX, "x"),loc(PosY, "y") +} + +// Typ has a comment. Its fields do not. +type Typ struct{ field string } //@loc(TypField, "field") + +func _() { + x := &Typ{} + _ = x.field //@def("field", TypField),hover("field", "field", hoverfield) +} + +func (p *Pos) Sum() int { //@loc(PosSum, "Sum") + return p.x + p.y //@hover("x", "x", hoverpx) +} + +func _() { + var p Pos + _ = p.Sum() //@def("()", PosSum),hover("()", `Sum`, hoverSum) +} +-- @aPackage/hover.md -- +-- @hoverDeclBlocka/hover.md -- +```go +type a struct { + x string +} +``` + +1st type declaration block +-- @hoverDeclBlockb/hover.md -- +```go +type b struct{} +``` + +b has a comment +-- @hoverDeclBlockc/hover.md -- +```go +type c struct { + f string +} +``` + +c is a struct +-- @hoverDeclBlockd/hover.md -- +```go +type d string +``` + +3rd type declaration block +-- @hoverDeclBlocke/hover.md -- +```go +type e struct { + f float64 +} +``` + +e has a comment +-- @hoverH/hover.md -- +```go +type H interface { + Goodbye() +} +``` + +[`a.H` on pkg.go.dev](https://pkg.go.dev/mod.com#H) +-- @hoverI/hover.md -- +```go +type I interface { + B() + J +} +``` + +[`a.I` on pkg.go.dev](https://pkg.go.dev/mod.com#I) +-- @hoverJ/hover.md -- +```go +type J interface { + Hello() +} +``` + +[`a.J` on pkg.go.dev](https://pkg.go.dev/mod.com#J) +-- @hoverSum/hover.md -- +```go +func (*Pos).Sum() int +``` + +[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/mod.com#Pos.Sum) +-- @hoverTestA/hover.md -- +```go +func TestA(t *testing.T) +``` +-- @hoverfield/hover.md -- +```go +field field string +``` +-- @hoverg/hover.md -- +```go +const g untyped int = 1 +``` + +When I hover on g, I should see this comment. +-- @hoverh/hover.md -- +```go +const h untyped int = 2 +``` + +Constant block. When I hover on h, I should see this comment. +-- @hoverpx/hover.md -- +```go +field x int +``` + +@loc(PosX, "x"),loc(PosY, "y") +-- @hoverx/hover.md -- +```go +var x string +``` + +x is a variable. +-- @hovery/hover.md -- +```go +var y int +``` +-- @hoverz/hover.md -- +```go +var z string +``` + +z is a variable too. diff --git a/gopls/internal/regtest/marker/testdata/hover/basiclit.txt b/gopls/internal/regtest/marker/testdata/hover/basiclit.txt index 30fe16e21cc..32527420d01 100644 --- a/gopls/internal/regtest/marker/testdata/hover/basiclit.txt +++ b/gopls/internal/regtest/marker/testdata/hover/basiclit.txt @@ -17,7 +17,7 @@ func _() { _ = 0x0001F30A //@hover("0x0001F30A", "0x0001F30A", waterWave) _ = "foo \U0001F30A bar" //@hover("\\U0001F30A", "\\U0001F30A", waterWave) - _ = '\x7E' //@hover("'\\x7E'", `'\x7E'`, tilde) + _ = '\x7E' //@hover("'\\x7E'", "'\\x7E'", tilde) _ = "foo \x7E bar" //@hover("\\x7E", "\\x7E", tilde) _ = "foo \a bar" //@hover("\\a", "\\a", control) @@ -29,10 +29,10 @@ func _() { _ = "foo\173bar\u2211baz" //@hover("\\u2211","\\u2211", summation) // search for runes in string only if there is an escaped sequence - _ = "hello" //@hover("\"hello\"", _, _) + _ = "hello" //@hover(`"hello"`, _, _) // incorrect escaped rune sequences - _ = '\0' //@hover("'\\0'", _, _) + _ = '\0' //@hover("'\\0'", _, _),diag(re`\\0()'`, re"illegal character") _ = '\u22111' //@hover("'\\u22111'", _, _) _ = '\U00110000' //@hover("'\\U00110000'", _, _) _ = '\u12e45'//@hover("'\\u12e45'", _, _) diff --git a/gopls/internal/regtest/marker/testdata/hover/const.txt b/gopls/internal/regtest/marker/testdata/hover/const.txt new file mode 100644 index 00000000000..cdb0e51e27d --- /dev/null +++ b/gopls/internal/regtest/marker/testdata/hover/const.txt @@ -0,0 +1,18 @@ +This test checks hovering over constants. +-- go.mod -- +module mod.com + +go 1.18 +-- c.go -- +package c + +const X = 0 //@hover("X", "X", bX) +-- @bX/hover.md -- +```go +const X untyped int = 0 +``` + +@hover("X", "X", bX) + + +[`c.X` on pkg.go.dev](https://pkg.go.dev/mod.com#X) diff --git a/gopls/internal/regtest/marker/testdata/hover/generics.txt b/gopls/internal/regtest/marker/testdata/hover/generics.txt index 137981f3813..2c526d82b97 100644 --- a/gopls/internal/regtest/marker/testdata/hover/generics.txt +++ b/gopls/internal/regtest/marker/testdata/hover/generics.txt @@ -35,7 +35,8 @@ func app[S interface{ ~[]E }, E interface{}](s S, e E) S { func _() { _ = app[[]int] //@hover("app", "app", appint) _ = app[[]int, int] //@hover("app", "app", appint) - _ = app[[]int]([]int{}, 0) //@hover("app", "app", appint) + // TODO(rfindley): eliminate this diagnostic. + _ = app[[]int]([]int{}, 0) //@hover("app", "app", appint),diag("[[]int]", re"unnecessary type arguments") _ = app([]int{}, 0) //@hover("app", "app", appint) } -- @ValueQ/hover.md -- diff --git a/gopls/internal/regtest/marker/testdata/hover/std.txt b/gopls/internal/regtest/marker/testdata/hover/std.txt new file mode 100644 index 00000000000..a526b5211eb --- /dev/null +++ b/gopls/internal/regtest/marker/testdata/hover/std.txt @@ -0,0 +1,80 @@ +This test checks hover results for built-in or standard library symbols. + +It uses synopsis documentation as full documentation for some of these +built-ins varies across Go versions, where as it just so happens that the +synopsis does not. + +In the future we may need to limit this test to the latest Go version to avoid +documentation churn. +-- settings.json -- +{ + "hoverKind": "SynopsisDocumentation" +} +-- go.mod -- +module mod.com + +go 1.18 +-- std.go -- +package std + +import ( + "fmt" + "go/types" + "sync" +) + +func _() { + var err error //@loc(err, "err") + fmt.Printf("%v", err) //@def("err", err) + + var _ string //@hover("string", "string", hoverstring) + _ = make([]int, 0) //@hover("make", "make", hovermake) + + var mu sync.Mutex + mu.Lock() //@hover("Lock", "Lock", hoverLock) + + var typ *types.Named //@hover("types", "types", hoverTypes) + typ.Obj().Name() //@hover("Name", "Name", hoverName) +} +-- @hoverLock/hover.md -- +```go +func (*sync.Mutex).Lock() +``` + +Lock locks m. + + +[`(sync.Mutex).Lock` on pkg.go.dev](https://pkg.go.dev/sync#Mutex.Lock) +-- @hoverName/hover.md -- +```go +func (*types.object).Name() string +``` + +Name returns the object's (package-local, unqualified) name. + + +[`(types.TypeName).Name` on pkg.go.dev](https://pkg.go.dev/go/types#TypeName.Name) +-- @hoverTypes/hover.md -- +```go +package types ("go/types") +``` + +[`types` on pkg.go.dev](https://pkg.go.dev/go/types) +-- @hovermake/hover.md -- +```go +func make(t Type, size ...int) Type +``` + +The make built-in function allocates and initializes an object of type slice, map, or chan (only). + + +[`make` on pkg.go.dev](https://pkg.go.dev/builtin#make) +-- @hoverstring/hover.md -- +```go +type string string +``` + +string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. + + +[`string` on pkg.go.dev](https://pkg.go.dev/builtin#string)