gopls/internal/lsp/fake: fix EOF bug in applyEdits
The previous logic would map EOF to a byte offset one beyond
the actual file length.
Fixed golang/go#57627
Change-Id: I6b2e33e1195bbf567752c0e13164e234fd1457a6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/460856
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
diff --git a/gopls/internal/lsp/fake/edit.go b/gopls/internal/lsp/fake/edit.go
index 3eb13ea..7f15a41 100644
--- a/gopls/internal/lsp/fake/edit.go
+++ b/gopls/internal/lsp/fake/edit.go
@@ -114,26 +114,33 @@
src := strings.Join(lines, "\n")
// Build a table of byte offset of start of each line.
- lineOffset := make([]int, len(lines)+1)
+ lineOffset := make([]int, len(lines))
offset := 0
for i, line := range lines {
lineOffset[i] = offset
offset += len(line) + len("\n")
}
- lineOffset[len(lines)] = offset // EOF
- var badCol error
+ var posErr error
posToOffset := func(pos Pos) int {
- offset := lineOffset[pos.Line]
// Convert pos.Column (runes) to a UTF-8 byte offset.
- if pos.Line < len(lines) {
- for i := 0; i < pos.Column; i++ {
- r, sz := utf8.DecodeRuneInString(src[offset:])
- if r == '\n' && badCol == nil {
- badCol = fmt.Errorf("bad column")
- }
- offset += sz
+ if pos.Line > len(lines) {
+ posErr = fmt.Errorf("bad line")
+ return 0
+ }
+ if pos.Line == len(lines) {
+ if pos.Column > 0 {
+ posErr = fmt.Errorf("bad column")
}
+ return len(src) // EOF
+ }
+ offset := lineOffset[pos.Line]
+ for i := 0; i < pos.Column; i++ {
+ r, sz := utf8.DecodeRuneInString(src[offset:])
+ if r == '\n' && posErr == nil {
+ posErr = fmt.Errorf("bad column")
+ }
+ offset += sz
}
return offset
}
@@ -153,5 +160,5 @@
return nil, err
}
- return strings.Split(patched, "\n"), badCol
+ return strings.Split(patched, "\n"), posErr
}
diff --git a/gopls/internal/lsp/fake/edit_test.go b/gopls/internal/lsp/fake/edit_test.go
index f87d921..fc42117 100644
--- a/gopls/internal/lsp/fake/edit_test.go
+++ b/gopls/internal/lsp/fake/edit_test.go
@@ -47,6 +47,23 @@
want: "ABC\nD12\n345\nJKL",
},
{
+ label: "regression test for issue #57627",
+ content: "go 1.18\nuse moda/a",
+ edits: []Edit{
+ {
+ Start: Pos{Line: 1, Column: 0},
+ End: Pos{Line: 1, Column: 0},
+ Text: "\n",
+ },
+ {
+ Start: Pos{Line: 2, Column: 0},
+ End: Pos{Line: 2, Column: 0},
+ Text: "\n",
+ },
+ },
+ want: "go 1.18\n\nuse moda/a\n",
+ },
+ {
label: "end before start",
content: "ABC\nDEF\nGHI\nJKL",
edits: []Edit{{
diff --git a/gopls/internal/regtest/modfile/modfile_test.go b/gopls/internal/regtest/modfile/modfile_test.go
index 08ab21e..1d2ade2 100644
--- a/gopls/internal/regtest/modfile/modfile_test.go
+++ b/gopls/internal/regtest/modfile/modfile_test.go
@@ -1185,3 +1185,17 @@
env.Await(EmptyDiagnostics("go.mod"))
})
}
+
+// This is a regression test for a bug in the line-oriented implementation
+// of the "apply diffs" operation used by the fake editor.
+func TestIssue57627(t *testing.T) {
+ const files = `
+-- go.work --
+package main
+`
+ Run(t, files, func(t *testing.T, env *Env) {
+ env.OpenFile("go.work")
+ env.SetBufferContent("go.work", "go 1.18\nuse moda/a")
+ env.SaveBuffer("go.work") // doesn't fail
+ })
+}