gopls/internal/golang: fix crash in ExtractToNewFile

The crash in ExtractToFile is caused by a known
bug in the parser (#57490): when a declaration at EOF
is not correctly terminated, the parser error recovery
will add the missing closing token, which makes the end
position of that node one more than EOF.

Example:
func foo() { var x // FuncDecl.End() = EOF + 1

Then in ExtractToFile, end - fileStart is out of bounds.

In CL 559436, we added a workaround so that methods
that depend on offsets/positions will adjust the values
if needed to avoid this out of bounds issue.

If we use safetoken.Offsets to calculate the bounds
instead of directly using
pgf.Src[start-fileStart : end-fileStart]
then the offsets are adjusted for us. Instead of
reporting a bug for the out of bounds error,
ExtractToFile will fail to format the extracted
source and return a more relevant error to the user.

Fixes golang/go#70553

Change-Id: I2ef7f2d5e880e3e73e04da3f68c689887571d973
Reviewed-on: https://go-review.googlesource.com/c/tools/+/798422
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Madeline Kalil <mkalil@google.com>
diff --git a/gopls/internal/golang/extracttofile.go b/gopls/internal/golang/extracttofile.go
index 9bd3d5b..054220f 100644
--- a/gopls/internal/golang/extracttofile.go
+++ b/gopls/internal/golang/extracttofile.go
@@ -98,8 +98,6 @@
 	if !ok {
 		return nil, fmt.Errorf("invalid selection")
 	}
-	pgf.CheckPos(start) // #70553
-	// Inv: start is valid wrt pgf.Tok.
 
 	// select trailing empty lines
 	offset, err := safetoken.Offset(pgf.Tok, end)
@@ -109,21 +107,10 @@
 	rest := pgf.Src[offset:]
 	spaces := len(rest) - len(bytes.TrimLeft(rest, " \t\n"))
 	end += token.Pos(spaces)
-	pgf.CheckPos(end) // #70553
-	if !(start <= end) {
-		bug.Reportf("start: not before end")
+	src, err := pgf.PosText(start, end)
+	if err != nil {
+		return nil, err
 	}
-	// Inv: end is valid wrt pgf.Tok; env >= start.
-	fileStart := pgf.File.FileStart
-	pgf.CheckPos(fileStart) // #70553
-	if !(0 <= start-fileStart) {
-		bug.Reportf("start: out of bounds")
-	}
-	if !(int(end-fileStart) <= len(pgf.Src)) {
-		bug.Reportf("end: out of bounds")
-	}
-	// Inv: 0 <= start-fileStart <= end-fileStart <= len(Src).
-	src := pgf.Src[start-fileStart : end-fileStart]
 
 	replaceRange, err := pgf.PosRange(start, end)
 	if err != nil {
@@ -180,7 +167,7 @@
 
 	newFileContent, err := format.Source(buf.Bytes())
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to format extracted source: %w", err)
 	}
 
 	return []protocol.DocumentChange{
diff --git a/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt b/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
index 5577b5e..950bde1 100644
--- a/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
+++ b/gopls/internal/test/marker/testdata/codeaction/extracttofile.txt
@@ -2,6 +2,7 @@
 
 -- flags --
 -ignore_extra_diags
+-errors_ok
 
 -- go.mod --
 module golang.org/lsptests/extracttofile
@@ -349,3 +350,10 @@
 -// docs
 -const C = "" //@codeaction("const", "refactor.extract.toNewFile", edit=copyrightandbuildconstraint)
 +//@codeaction("const", "refactor.extract.toNewFile", edit=copyrightandbuildconstraint)
+
+-- parsererror.go --
+package main
+
+func foo() { //@codeaction("foo", "refactor.extract.toNewFile", err=re"failed to format extracted source")
+	for i := struct
+