gopls/internal/lsp: tolerate new 'imported and not used' error message

Tolerate the new form of the "... imported but not used" error message,
to allow landing this change in go/types.

Along the way, improve the test output when comparing diagnostics, and
formatting results.

For golang/go#54845

Change-Id: I998d539f82e0f70c85bdb4c40858be5e01dd08b6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/435355
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Findley 2022-09-27 13:04:59 -04:00 committed by Gopher Robot
parent eb45e986a7
commit 10e9d3cefa
18 changed files with 113 additions and 106 deletions

View File

@ -45,6 +45,7 @@ var Analyzer = &analysis.Analyzer{
RunDespiteErrors: true,
}
// The prefix for this error message changed in Go 1.20.
var undeclaredNamePrefixes = []string{"undeclared name: ", "undefined: "}
func run(pass *analysis.Pass) (interface{}, error) {

View File

@ -14,55 +14,55 @@ type A struct {
}
func singleAssignment() {
v := "s" // want `v declared but not used`
v := "s" // want `v declared (and|but) not used`
s := []int{ // want `s declared but not used`
s := []int{ // want `s declared (and|but) not used`
1,
2,
}
a := func(s string) bool { // want `a declared but not used`
a := func(s string) bool { // want `a declared (and|but) not used`
return false
}
if 1 == 1 {
s := "v" // want `s declared but not used`
s := "v" // want `s declared (and|but) not used`
}
panic("I should survive")
}
func noOtherStmtsInBlock() {
v := "s" // want `v declared but not used`
v := "s" // want `v declared (and|but) not used`
}
func partOfMultiAssignment() {
f, err := os.Open("file") // want `f declared but not used`
f, err := os.Open("file") // want `f declared (and|but) not used`
panic(err)
}
func sideEffects(cBool chan bool, cInt chan int) {
b := <-c // want `b declared but not used`
s := fmt.Sprint("") // want `s declared but not used`
a := A{ // want `a declared but not used`
b := <-c // want `b declared (and|but) not used`
s := fmt.Sprint("") // want `s declared (and|but) not used`
a := A{ // want `a declared (and|but) not used`
b: func() int {
return 1
}(),
}
c := A{<-cInt} // want `c declared but not used`
d := fInt() + <-cInt // want `d declared but not used`
e := fBool() && <-cBool // want `e declared but not used`
f := map[int]int{ // want `f declared but not used`
c := A{<-cInt} // want `c declared (and|but) not used`
d := fInt() + <-cInt // want `d declared (and|but) not used`
e := fBool() && <-cBool // want `e declared (and|but) not used`
f := map[int]int{ // want `f declared (and|but) not used`
fInt(): <-cInt,
}
g := []int{<-cInt} // want `g declared but not used`
h := func(s string) {} // want `h declared but not used`
i := func(s string) {}() // want `i declared but not used`
g := []int{<-cInt} // want `g declared (and|but) not used`
h := func(s string) {} // want `h declared (and|but) not used`
i := func(s string) {}() // want `i declared (and|but) not used`
}
func commentAbove() {
// v is a variable
v := "s" // want `v declared but not used`
v := "s" // want `v declared (and|but) not used`
}
func fBool() bool {

View File

@ -24,26 +24,26 @@ func noOtherStmtsInBlock() {
}
func partOfMultiAssignment() {
_, err := os.Open("file") // want `f declared but not used`
_, err := os.Open("file") // want `f declared (and|but) not used`
panic(err)
}
func sideEffects(cBool chan bool, cInt chan int) {
<-c // want `b declared but not used`
fmt.Sprint("") // want `s declared but not used`
A{ // want `a declared but not used`
<-c // want `b declared (and|but) not used`
fmt.Sprint("") // want `s declared (and|but) not used`
A{ // want `a declared (and|but) not used`
b: func() int {
return 1
}(),
}
A{<-cInt} // want `c declared but not used`
fInt() + <-cInt // want `d declared but not used`
fBool() && <-cBool // want `e declared but not used`
map[int]int{ // want `f declared but not used`
A{<-cInt} // want `c declared (and|but) not used`
fInt() + <-cInt // want `d declared (and|but) not used`
fBool() && <-cBool // want `e declared (and|but) not used`
map[int]int{ // want `f declared (and|but) not used`
fInt(): <-cInt,
}
[]int{<-cInt} // want `g declared but not used`
func(s string) {}() // want `i declared but not used`
[]int{<-cInt} // want `g declared (and|but) not used`
func(s string) {}() // want `i declared (and|but) not used`
}
func commentAbove() {

View File

@ -5,17 +5,17 @@
package decl
func a() {
var b, c bool // want `b declared but not used`
var b, c bool // want `b declared (and|but) not used`
panic(c)
if 1 == 1 {
var s string // want `s declared but not used`
var s string // want `s declared (and|but) not used`
}
}
func b() {
// b is a variable
var b bool // want `b declared but not used`
var b bool // want `b declared (and|but) not used`
}
func c() {
@ -23,7 +23,7 @@ func c() {
d string
// some comment for c
c bool // want `c declared but not used`
c bool // want `c declared (and|but) not used`
)
panic(d)

View File

@ -5,7 +5,7 @@
package decl
func a() {
var c bool // want `b declared but not used`
var c bool // want `b declared (and|but) not used`
panic(c)
if 1 == 1 {

View File

@ -34,15 +34,18 @@ var Analyzer = &analysis.Analyzer{
type fixesForError map[types.Error][]analysis.SuggestedFix
const unusedVariableSuffix = " declared but not used"
// The suffix for this error message changed in Go 1.20.
var unusedVariableSuffixes = []string{" declared and not used", " declared but not used"}
func run(pass *analysis.Pass) (interface{}, error) {
for _, typeErr := range analysisinternal.GetTypeErrors(pass) {
if strings.HasSuffix(typeErr.Msg, unusedVariableSuffix) {
varName := strings.TrimSuffix(typeErr.Msg, unusedVariableSuffix)
err := runForError(pass, typeErr, varName)
if err != nil {
return nil, err
for _, suffix := range unusedVariableSuffixes {
if strings.HasSuffix(typeErr.Msg, suffix) {
varName := strings.TrimSuffix(typeErr.Msg, suffix)
err := runForError(pass, typeErr, varName)
if err != nil {
return nil, err
}
}
}
}

View File

@ -59,7 +59,5 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnost
diag.Severity = 0
}
if diff := tests.DiffDiagnostics(uri, want, got); diff != "" {
t.Error(diff)
}
tests.CompareDiagnostics(t, uri, want, got)
}

View File

@ -223,9 +223,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnost
v := r.server.session.View(r.data.Config.Dir)
r.collectDiagnostics(v)
got := append([]*source.Diagnostic(nil), r.diagnostics[uri]...) // copy
if diff := tests.DiffDiagnostics(uri, want, got); diff != "" {
t.Error(diff)
}
tests.CompareDiagnostics(t, uri, want, got)
}
func (r *runner) FoldingRanges(t *testing.T, spn span.Span) {
@ -416,8 +414,8 @@ func (r *runner) Format(t *testing.T, spn span.Span) {
t.Error(err)
}
got := diff.ApplyEdits(string(m.Content), sedits)
if gofmted != got {
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
if diff := compare.Text(gofmted, got); diff != "" {
t.Errorf("format failed for %s (-want +got):\n%s", filename, diff)
}
}

View File

@ -156,9 +156,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnost
if err != nil {
t.Fatal(err)
}
if diff := tests.DiffDiagnostics(fileID.URI, want, got); diff != "" {
t.Error(diff)
}
tests.CompareDiagnostics(t, fileID.URI, want, got)
}
func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {

View File

@ -9,7 +9,7 @@ func stuff() { //@item(stuff, "stuff", "func()", "func")
x := "heeeeyyyy"
random2(x) //@diag("x", "compiler", "cannot use x \\(variable of type string\\) as int value in argument to random2", "error")
random2(1) //@complete("dom", random, random2, random3)
y := 3 //@diag("y", "compiler", "y declared but not used", "error")
y := 3 //@diag("y", "compiler", "y declared (and|but) not used", "error")
}
type bob struct { //@item(bob, "bob", "struct{...}", "struct")

View File

@ -14,9 +14,9 @@ func random() int { //@item(random, "random", "func() int", "func")
}
func random2(y int) int { //@item(random2, "random2", "func(y int) int", "func"),item(bad_y_param, "y", "int", "var")
x := 6 //@item(x, "x", "int", "var"),diag("x", "compiler", "x declared but not used", "error")
var q blah //@item(q, "q", "blah", "var"),diag("q", "compiler", "q declared but not used", "error"),diag("blah", "compiler", "(undeclared name|undefined): blah", "error")
var t **blob //@item(t, "t", "**blob", "var"),diag("t", "compiler", "t declared but not used", "error"),diag("blob", "compiler", "(undeclared name|undefined): blob", "error")
x := 6 //@item(x, "x", "int", "var"),diag("x", "compiler", "x declared (and|but) not used", "error")
var q blah //@item(q, "q", "blah", "var"),diag("q", "compiler", "q declared (and|but) not used", "error"),diag("blah", "compiler", "(undeclared name|undefined): blah", "error")
var t **blob //@item(t, "t", "**blob", "var"),diag("t", "compiler", "t declared (and|but) not used", "error"),diag("blob", "compiler", "(undeclared name|undefined): blob", "error")
//@complete("", q, t, x, bad_y_param, global_a, bob, random, random2, random3, stateFunc, stuff)
return y
@ -25,10 +25,10 @@ func random2(y int) int { //@item(random2, "random2", "func(y int) int", "func")
func random3(y ...int) { //@item(random3, "random3", "func(y ...int)", "func"),item(y_variadic_param, "y", "[]int", "var")
//@complete("", y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff)
var ch chan (favType1) //@item(ch, "ch", "chan (favType1)", "var"),diag("ch", "compiler", "ch declared but not used", "error"),diag("favType1", "compiler", "(undeclared name|undefined): favType1", "error")
var m map[keyType]int //@item(m, "m", "map[keyType]int", "var"),diag("m", "compiler", "m declared but not used", "error"),diag("keyType", "compiler", "(undeclared name|undefined): keyType", "error")
var arr []favType2 //@item(arr, "arr", "[]favType2", "var"),diag("arr", "compiler", "arr declared but not used", "error"),diag("favType2", "compiler", "(undeclared name|undefined): favType2", "error")
var fn1 func() badResult //@item(fn1, "fn1", "func() badResult", "var"),diag("fn1", "compiler", "fn1 declared but not used", "error"),diag("badResult", "compiler", "(undeclared name|undefined): badResult", "error")
var fn2 func(badParam) //@item(fn2, "fn2", "func(badParam)", "var"),diag("fn2", "compiler", "fn2 declared but not used", "error"),diag("badParam", "compiler", "(undeclared name|undefined): badParam", "error")
var ch chan (favType1) //@item(ch, "ch", "chan (favType1)", "var"),diag("ch", "compiler", "ch declared (and|but) not used", "error"),diag("favType1", "compiler", "(undeclared name|undefined): favType1", "error")
var m map[keyType]int //@item(m, "m", "map[keyType]int", "var"),diag("m", "compiler", "m declared (and|but) not used", "error"),diag("keyType", "compiler", "(undeclared name|undefined): keyType", "error")
var arr []favType2 //@item(arr, "arr", "[]favType2", "var"),diag("arr", "compiler", "arr declared (and|but) not used", "error"),diag("favType2", "compiler", "(undeclared name|undefined): favType2", "error")
var fn1 func() badResult //@item(fn1, "fn1", "func() badResult", "var"),diag("fn1", "compiler", "fn1 declared (and|but) not used", "error"),diag("badResult", "compiler", "(undeclared name|undefined): badResult", "error")
var fn2 func(badParam) //@item(fn2, "fn2", "func(badParam)", "var"),diag("fn2", "compiler", "fn2 declared (and|but) not used", "error"),diag("badParam", "compiler", "(undeclared name|undefined): badParam", "error")
//@complete("", arr, ch, fn1, fn2, m, y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff)
}

View File

@ -9,7 +9,7 @@ import (
func hello() {
var x int //@diag("x", "compiler", "x declared but not used", "error")
var x int //@diag("x", "compiler", "x declared (and|but) not used", "error")
}
func hi() {

View File

@ -11,7 +11,7 @@ func hello() {
var x int //@diag("x", "compiler", "x declared but not used", "error")
var x int //@diag("x", "compiler", "x declared (and|but) not used", "error")
}
func hi() {

View File

@ -3,5 +3,5 @@ package generated
// Code generated by generator.go. DO NOT EDIT.
func _() {
var y int //@diag("y", "compiler", "y declared but not used", "error")
var y int //@diag("y", "compiler", "y declared (and|but) not used", "error")
}

View File

@ -1,5 +1,5 @@
package generated
func _() {
var x int //@diag("x", "compiler", "x declared but not used", "error")
var x int //@diag("x", "compiler", "x declared (and|but) not used", "error")
}

View File

@ -8,7 +8,7 @@ import (
)
func TestSomething(t *testing.T) { //@item(TestSomething, "TestSomething(t *testing.T)", "", "func")
var x int //@mark(testyX, "x"),diag("x", "compiler", "x declared but not used", "error"),refs("x", testyX)
var x int //@mark(testyX, "x"),diag("x", "compiler", "x declared (and|but) not used", "error"),refs("x", testyX)
a() //@mark(testyA, "a")
}

View File

@ -9,11 +9,13 @@ import (
"context"
"fmt"
"go/token"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"testing"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
@ -65,43 +67,69 @@ func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []proto
return ""
}
// DiffDiagnostics prints the diff between expected and actual diagnostics test
// results. If the sole expectation is "no_diagnostics", the check is suppressed.
// The Message field of each want element must be a regular expression.
func DiffDiagnostics(uri span.URI, want, got []*source.Diagnostic) string {
// CompareDiagnostics reports testing errors to t when the diagnostic set got
// does not match want. If the sole expectation has source "no_diagnostics",
// the test expects that no diagnostics were received for the given document.
func CompareDiagnostics(t *testing.T, uri span.URI, want, got []*source.Diagnostic) {
t.Helper()
fileName := path.Base(string(uri))
// A special case to test that there are no diagnostics for a file.
if len(want) == 1 && want[0].Source == "no_diagnostics" {
if len(got) != 0 {
return fmt.Sprintf("expected no diagnostics for %s, got %v", uri, got)
want = nil
}
// Build a helper function to match an actual diagnostic to an overlapping
// expected diagnostic (if any).
unmatched := make([]*source.Diagnostic, len(want))
copy(unmatched, want)
source.SortDiagnostics(unmatched)
match := func(g *source.Diagnostic) *source.Diagnostic {
// Find the last expected diagnostic d for which start(d) < end(g), and
// check to see if it overlaps.
i := sort.Search(len(unmatched), func(i int) bool {
d := unmatched[i]
// See rangeOverlaps: if a range is a single point, we consider End to be
// included in the range...
if g.Range.Start == g.Range.End {
return protocol.ComparePosition(d.Range.Start, g.Range.End) > 0
}
// ...otherwise the end position of a range is not included.
return protocol.ComparePosition(d.Range.Start, g.Range.End) >= 0
})
if i == 0 {
return nil
}
return ""
w := unmatched[i-1]
if rangeOverlaps(w.Range, g.Range) {
unmatched = append(unmatched[:i-1], unmatched[i:]...)
return w
}
return nil
}
source.SortDiagnostics(want)
source.SortDiagnostics(got)
if len(got) != len(want) {
// TODO(adonovan): print the actual difference, not the difference in length!
return summarizeDiagnostics(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
}
for i, w := range want {
g := got[i]
if !rangeOverlaps(g.Range, w.Range) {
return summarizeDiagnostics(i, uri, want, got, "got Range %v, want overlap with %v", g.Range, w.Range)
for _, g := range got {
w := match(g)
if w == nil {
t.Errorf("%s:%s: unexpected diagnostic %q", fileName, g.Range, g.Message)
continue
}
if match, err := regexp.MatchString(w.Message, g.Message); err != nil {
return summarizeDiagnostics(i, uri, want, got, "%s: invalid regular expression %q: %v", w.Range.Start, w.Message, err)
t.Errorf("%s:%s: invalid regular expression %q: %v", fileName, w.Range.Start, w.Message, err)
} else if !match {
return summarizeDiagnostics(i, uri, want, got, "%s: got Message %q, want match for pattern %q", g.Range.Start, g.Message, w.Message)
t.Errorf("%s:%s: got Message %q, want match for pattern %q", fileName, g.Range.Start, g.Message, w.Message)
}
if w.Severity != g.Severity {
return summarizeDiagnostics(i, uri, want, got, "%s: got Severity %v, want %v", g.Range.Start, g.Severity, w.Severity)
t.Errorf("%s:%s: got Severity %v, want %v", fileName, g.Range.Start, g.Severity, w.Severity)
}
if w.Source != g.Source {
return summarizeDiagnostics(i, uri, want, got, "%s: got Source %v, want %v", g.Range.Start, g.Source, w.Source)
t.Errorf("%s:%s: got Source %v, want %v", fileName, g.Range.Start, g.Source, w.Source)
}
}
return ""
for _, w := range unmatched {
t.Errorf("%s:%s: unmatched diagnostic pattern %q", fileName, w.Range, w.Message)
}
}
// rangeOverlaps reports whether r1 and r2 overlap.
@ -125,25 +153,6 @@ func inRange(p protocol.Position, r protocol.Range) bool {
return false
}
func summarizeDiagnostics(i int, uri span.URI, want, got []*source.Diagnostic, reason string, args ...interface{}) string {
msg := &bytes.Buffer{}
fmt.Fprint(msg, "diagnostics failed")
if i >= 0 {
fmt.Fprintf(msg, " at %d", i)
}
fmt.Fprint(msg, " because of ")
fmt.Fprintf(msg, reason, args...)
fmt.Fprint(msg, ":\nexpected:\n")
for _, d := range want {
fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message)
}
fmt.Fprintf(msg, "got:\n")
for _, d := range got {
fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message)
}
return msg.String()
}
func DiffCodeLens(uri span.URI, want, got []protocol.CodeLens) string {
sortCodeLens(want)
sortCodeLens(got)

View File

@ -1215,7 +1215,7 @@ import (
env.Await(
OnceMet(
env.DoneWithOpen(),
env.DiagnosticAtRegexpWithMessage("a/main.go", "V", "declared but not used"),
env.DiagnosticAtRegexpWithMessage("a/main.go", "V", "not used"),
),
)
env.CloseBuffer("a/main.go")