internal/span: fix incorrect bounds check in ToOffset

token.File.LineStart panics if line < 1, but we were checking line < 0.
Surprising that this was not hit more often: it looks like we
pre-validate input except for parsed positions coming from the Go
command. It is possible that an older version of the Go command returned
invalid positions.

Also report a bug for one error condition in ToOffset: the position
returned by LineStart should always be valid.

Fixes golang/go#54006

Change-Id: I5965af9c62976b3e00b023512df334a8de943a3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/419109
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/internal/span/token.go b/internal/span/token.go
index cae696d..c35a512 100644
--- a/internal/span/token.go
+++ b/internal/span/token.go
@@ -176,15 +176,16 @@
 	return line, col, err
 }
 
-// ToOffset converts a 1-base line and utf-8 column index into a byte offset in
-// the file corresponding to tf.
+// ToOffset converts a 1-based line and utf-8 column index into a byte offset
+// in the file corresponding to tf.
 func ToOffset(tf *token.File, line, col int) (int, error) {
-	if line < 0 {
-		return -1, fmt.Errorf("line is not valid")
+	if line < 1 { // token.File.LineStart panics if line < 1
+		return -1, fmt.Errorf("invalid line: %d", line)
 	}
+
 	lineMax := tf.LineCount() + 1
 	if line > lineMax {
-		return -1, fmt.Errorf("line is beyond end of file %v", lineMax)
+		return -1, fmt.Errorf("line %d is beyond end of file %v", line, lineMax)
 	} else if line == lineMax {
 		if col > 1 {
 			return -1, fmt.Errorf("column is beyond end of file")
@@ -194,7 +195,9 @@
 	}
 	pos := tf.LineStart(line)
 	if !pos.IsValid() {
-		return -1, fmt.Errorf("line is not in file")
+		// bug.Errorf here because LineStart panics on out-of-bound input, and so
+		// should never return invalid positions.
+		return -1, bug.Errorf("line is not in file")
 	}
 	// we assume that column is in bytes here, and that the first byte of a
 	// line is at column 1