gopls/internal/golang: Definition: fix Windows bug wrt //go:embed The filepath.Match function requires an OS-separated pattern string. Gopls was passing it the UNIX-style forward-slash separated pattern even on Windows, causing subdirectories not to match. Fixes golang/go#77131 Change-Id: I63de3c1549195cdfb71db3b39d05cacebff95eaf Reviewed-on: https://go-review.googlesource.com/c/tools/+/734901 Reviewed-by: Madeline Kalil <mkalil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/internal/golang/embeddirective.go b/gopls/internal/golang/embeddirective.go index 33f4f5e..e6004e7 100644 --- a/gopls/internal/golang/embeddirective.go +++ b/gopls/internal/golang/embeddirective.go
@@ -22,16 +22,17 @@ // As such it indicates that other definitions could be worth checking. var ErrNoEmbed = errors.New("no embed directive found") -var errStopWalk = errors.New("stop walk") - // embedDefinition finds a file matching the embed directive at pos in the mapped file. -// If there is no embed directive at pos, returns ErrNoEmbed. +// If there is no embed directive at pos, returns [ErrNoEmbed]. // If multiple files match the embed pattern, one is picked at random. func embedDefinition(m *protocol.Mapper, pos protocol.Position) ([]protocol.Location, error) { pattern, _ := parseEmbedDirective(m, protocol.Range{Start: pos, End: pos}) if pattern == "" { return nil, ErrNoEmbed } + pattern = filepath.FromSlash(pattern) // Match requires OS separators + + errFound := errors.New("found") // Find the first matching file. var match string @@ -50,30 +51,23 @@ } if ok && !d.IsDir() { match = abs - return errStopWalk + return errFound } return nil }) - if err != nil && !errors.Is(err, errStopWalk) { - return nil, err + if err != nil && !errors.Is(err, errFound) { + return nil, err // bad pattern or I/O error } if match == "" { return nil, fmt.Errorf("%q does not match any files in %q", pattern, dir) } - - loc := protocol.Location{ - URI: protocol.URIFromPath(match), - Range: protocol.Range{ - Start: protocol.Position{Line: 0, Character: 0}, - }, - } - return []protocol.Location{loc}, nil + return []protocol.Location{{URI: protocol.URIFromPath(match)}}, nil } // parseEmbedDirective attempts to parse a go:embed directive argument that // contains the given range. -// If successful it return the directive argument and its range, else zero -// values are returned. +// If successful it returns the directive argument (a UNIX-style glob +// pattern) and its range, else zero values are returned. func parseEmbedDirective(m *protocol.Mapper, rng protocol.Range) (string, protocol.Range) { if rng.Start.Line != rng.End.Line { return "", protocol.Range{}
diff --git a/gopls/internal/test/integration/misc/definition_test.go b/gopls/internal/test/integration/misc/definition_test.go index f7024d1..43444cc 100644 --- a/gopls/internal/test/integration/misc/definition_test.go +++ b/gopls/internal/test/integration/misc/definition_test.go
@@ -9,6 +9,7 @@ "os" "path" "path/filepath" + "reflect" "regexp" "strings" "testing" @@ -692,3 +693,37 @@ }) } + +func TestDefinition_embedSeparator(t *testing.T) { + // This test checks that Definition on a //go:embed glob pattern reports + // the location of the (first) matching file. It is a regression test for + // a path-separator bug on Windows (https://go.dev/issue/77131). + // + // It can't be expressed as a marker test because it would require + // a @def marker within a //go:embed directive. + const src = ` +-- go.mod -- +module example.com +go 1.22 + +-- a/subdir/embed.txt -- +hello + +-- a/a.go -- +package a + +import _ "embed" + +//go:embed subdir/embed.txt +var _ string +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + loc := env.FirstDefinition(env.RegexpSearch("a/a.go", "subdir")) + got := fileLocations(env, []protocol.Location{loc}) + want := []string{"a/subdir/embed.txt:1"} + if !reflect.DeepEqual(got, want) { + t.Errorf("Definition at 'subdir' returned %v, want %v", got, want) + } + }) +}