From 24a13c6fade52381ab08896106c15e4dd1429fd1 Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Mon, 6 Feb 2023 18:08:12 -0500 Subject: [PATCH] gopls/internal/regtest: fill out features of the new marker tests Add missing features to the new marker test implementation, implement a few new markers, and port some tests to demonstrate the new structure. Additionally, improve UX following some experience working with these tests. Specifically: - Add support for settings.json. This was necessary for standard library hover, since full documentation was too verbose and varied across Go versions. - Ensure that the ordering of ordinary archive files is preserved. I kept having go.mod sorted below go files, which harms readability. - Add a helper to provide a nice location summary for test output, formatting both local and global (=archive-wide) positions. - Add support for both regexp and string locations conversion. - Add the loc marker, which is pre-processed to make named locations available to other markers. - Add the diag marker, which defines a 1:1 pairing between observed and expected diagnostics. - Add the def marker, which runs textDocument/definition. - Port around half of the godef tests, which include both def and hover markers. While doing so, try to extract related assertions into separate tests, to improve organization and documentation and reduce test size. Remaining tests will have to wait, as this CL was getting too big. For golang/go#54845 Change-Id: Id9fe22c00ebd1b3a96eeacc5c0e82fca9c95c680 Reviewed-on: https://go-review.googlesource.com/c/tools/+/465895 Run-TryBot: Robert Findley Reviewed-by: Alan Donovan TryBot-Result: Gopher Robot gopls-CI: kokoro --- gopls/internal/lsp/fake/editor.go | 3 + gopls/internal/lsp/regtest/expectation.go | 20 + gopls/internal/lsp/regtest/marker.go | 474 ++++++++++++----- gopls/internal/lsp/testdata/godef/a/a.go | 111 ---- .../internal/lsp/testdata/godef/a/a.go.golden | 230 --------- gopls/internal/lsp/testdata/godef/a/a_test.go | 8 - .../lsp/testdata/godef/a/a_test.go.golden | 26 - gopls/internal/lsp/testdata/godef/a/f.go | 1 + gopls/internal/lsp/testdata/godef/a/random.go | 31 -- .../lsp/testdata/godef/a/random.go.golden | 113 ----- gopls/internal/lsp/testdata/godef/b/b.go | 57 --- .../internal/lsp/testdata/godef/b/b.go.golden | 480 ------------------ gopls/internal/lsp/testdata/godef/b/c.go | 8 - .../internal/lsp/testdata/godef/b/c.go.golden | 76 --- .../internal/lsp/testdata/godef/b/c.go.saved | 7 - gopls/internal/lsp/testdata/godef/b/h.go | 10 - .../internal/lsp/testdata/godef/b/h.go.golden | 13 - .../internal/lsp/testdata/summary.txt.golden | 2 +- .../lsp/testdata/summary_go1.18.txt.golden | 2 +- .../marker/testdata/definition/embed.txt | 226 +++++++++ .../marker/testdata/definition/import.txt | 52 ++ .../marker/testdata/definition/misc.txt | 230 +++++++++ .../marker/testdata/hover/basiclit.txt | 6 +- .../regtest/marker/testdata/hover/const.txt | 18 + .../marker/testdata/hover/generics.txt | 3 +- .../regtest/marker/testdata/hover/std.txt | 80 +++ 26 files changed, 973 insertions(+), 1314 deletions(-) delete mode 100644 gopls/internal/lsp/testdata/godef/a/a.go delete mode 100644 gopls/internal/lsp/testdata/godef/a/a.go.golden delete mode 100644 gopls/internal/lsp/testdata/godef/a/a_test.go delete mode 100644 gopls/internal/lsp/testdata/godef/a/a_test.go.golden delete mode 100644 gopls/internal/lsp/testdata/godef/a/random.go delete mode 100644 gopls/internal/lsp/testdata/godef/a/random.go.golden delete mode 100644 gopls/internal/lsp/testdata/godef/b/b.go delete mode 100644 gopls/internal/lsp/testdata/godef/b/b.go.golden delete mode 100644 gopls/internal/lsp/testdata/godef/b/c.go delete mode 100644 gopls/internal/lsp/testdata/godef/b/c.go.golden delete mode 100644 gopls/internal/lsp/testdata/godef/b/c.go.saved delete mode 100644 gopls/internal/lsp/testdata/godef/b/h.go delete mode 100644 gopls/internal/lsp/testdata/godef/b/h.go.golden create mode 100644 gopls/internal/regtest/marker/testdata/definition/embed.txt create mode 100644 gopls/internal/regtest/marker/testdata/definition/import.txt create mode 100644 gopls/internal/regtest/marker/testdata/definition/misc.txt create mode 100644 gopls/internal/regtest/marker/testdata/hover/const.txt create mode 100644 gopls/internal/regtest/marker/testdata/hover/std.txt 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)