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)
+		}
+	})
+}