Skip to content

Commit 7ad3a0d

Browse files
committed
[gopls-release-branch.0.16] gopls/internal/golang/completion: fix package clause completion suffix
CL 585275 introduced a call to completion.Surrounding.Suffix that exposed a latent bug in package clause completions: the completion content was not being correctly computed as containing the cursor. Fix the heuristics for completing at "package<whitespace>|", so that the surrounding content is correct. As a result of this change the 'editRegexp' from some completion tests now no longer includes the newline, which also seems more correct. Fixes golang/go#68169 Change-Id: I32ea20a7f9f0096aef85ed77c64d3b4dfeedef45 Reviewed-on: https://go-review.googlesource.com/c/tools/+/594796 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com> Auto-Submit: Robert Findley <rfindley@google.com> (cherry picked from commit 008ed2c) Reviewed-on: https://go-review.googlesource.com/c/tools/+/595575
1 parent 02faa7b commit 7ad3a0d

File tree

3 files changed

+57
-36
lines changed

3 files changed

+57
-36
lines changed

gopls/internal/golang/completion/package.go

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -106,39 +106,20 @@ func packageCompletionSurrounding(pgf *parsego.File, offset int) (*Selection, er
106106

107107
// First, consider the possibility that we have a valid "package" keyword
108108
// with an empty package name ("package "). "package" is parsed as an
109-
// *ast.BadDecl since it is a keyword. This logic would allow "package" to
110-
// appear on any line of the file as long as it's the first code expression
111-
// in the file.
112-
lines := strings.Split(string(pgf.Src), "\n")
113-
cursorLine := safetoken.Line(tok, cursor)
114-
if cursorLine <= 0 || cursorLine > len(lines) {
115-
return nil, fmt.Errorf("invalid line number")
109+
// *ast.BadDecl since it is a keyword.
110+
start, err := safetoken.Offset(tok, expr.Pos())
111+
if err != nil {
112+
return nil, err
116113
}
117-
if safetoken.StartPosition(fset, expr.Pos()).Line == cursorLine {
118-
words := strings.Fields(lines[cursorLine-1])
119-
if len(words) > 0 && words[0] == PACKAGE {
120-
content := PACKAGE
121-
// Account for spaces if there are any.
122-
if len(words) > 1 {
123-
content += " "
124-
}
125-
126-
start := expr.Pos()
127-
end := token.Pos(int(expr.Pos()) + len(content) + 1)
128-
// We have verified that we have a valid 'package' keyword as our
129-
// first expression. Ensure that cursor is in this keyword or
130-
// otherwise fallback to the general case.
131-
if cursor >= start && cursor <= end {
132-
return &Selection{
133-
content: content,
134-
cursor: cursor,
135-
tokFile: tok,
136-
start: start,
137-
end: end,
138-
mapper: m,
139-
}, nil
140-
}
141-
}
114+
if offset > start && string(bytes.TrimRight(pgf.Src[start:offset], " ")) == PACKAGE {
115+
return &Selection{
116+
content: string(pgf.Src[start:offset]),
117+
cursor: cursor,
118+
tokFile: tok,
119+
start: expr.Pos(),
120+
end: cursor,
121+
mapper: m,
122+
}, nil
142123
}
143124

144125
// If the cursor is after the start of the expression, no package

gopls/internal/test/integration/completion/completion_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ package
121121
want: nil,
122122
},
123123
{
124-
name: "package completion after keyword 'package'",
124+
name: "package completion after package keyword",
125125
filename: "fruits/testfile2.go",
126126
triggerRegexp: "package()",
127127
want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
128-
editRegexp: "package\n",
128+
editRegexp: "package",
129129
},
130130
{
131131
name: "package completion with 'pac' prefix",
@@ -164,14 +164,14 @@ package
164164
filename: "123f_r.u~its-123/testfile.go",
165165
triggerRegexp: "package()",
166166
want: []string{"package fruits123", "package fruits123_test", "package main"},
167-
editRegexp: "package\n",
167+
editRegexp: "package",
168168
},
169169
{
170170
name: "package completion for invalid dir name",
171171
filename: ".invalid-dir@-name/testfile.go",
172172
triggerRegexp: "package()",
173173
want: []string{"package main"},
174-
editRegexp: "package\n",
174+
editRegexp: "package",
175175
},
176176
} {
177177
t.Run(tc.name, func(t *testing.T) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package completion
6+
7+
import (
8+
"testing"
9+
10+
. "golang.org/x/tools/gopls/internal/test/integration"
11+
)
12+
13+
func TestPackageCompletionCrash_Issue68169(t *testing.T) {
14+
// This test reproduces the scenario of golang/go#68169, a crash in
15+
// completion.Selection.Suffix.
16+
//
17+
// The file content here is extracted from the issue.
18+
const files = `
19+
-- go.mod --
20+
module example.com
21+
22+
go 1.18
23+
-- playdos/play.go --
24+
package
25+
`
26+
27+
Run(t, files, func(t *testing.T, env *Env) {
28+
env.OpenFile("playdos/play.go")
29+
// Previously, this call would crash gopls as it was incorrectly computing
30+
// the surrounding completion suffix.
31+
completions := env.Completion(env.RegexpSearch("playdos/play.go", "package ()"))
32+
if len(completions.Items) == 0 {
33+
t.Fatal("Completion() returned empty results")
34+
}
35+
// Sanity check: we should get package clause compeltion.
36+
if got, want := completions.Items[0].Label, "package playdos"; got != want {
37+
t.Errorf("Completion()[0].Label == %s, want %s", got, want)
38+
}
39+
})
40+
}

0 commit comments

Comments
 (0)