gopls/internal/regtest: add a test for using staticcheck with generics

For golang/go#52159

Change-Id: I08120331b7f5c9eb06feac0d0eeb76a9a7b629df
Reviewed-on: https://go-review.googlesource.com/c/tools/+/399914
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Dylan Le <dungtuanle@google.com>
diff --git a/gopls/internal/regtest/misc/staticcheck_test.go b/gopls/internal/regtest/misc/staticcheck_test.go
new file mode 100644
index 0000000..94bb399
--- /dev/null
+++ b/gopls/internal/regtest/misc/staticcheck_test.go
@@ -0,0 +1,78 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package misc
+
+import (
+	"testing"
+
+	"golang.org/x/tools/internal/testenv"
+
+	. "golang.org/x/tools/internal/lsp/regtest"
+)
+
+func TestStaticcheckGenerics(t *testing.T) {
+	testenv.NeedsGo1Point(t, 18) // generics were introduced in Go 1.18
+
+	const files = `
+-- go.mod --
+module mod.com
+
+go 1.18
+-- a/a.go --
+package a
+
+import (
+	"errors"
+	"sort"
+	"strings"
+)
+
+func Zero[P any]() P {
+	var p P
+	return p
+}
+
+type Inst[P any] struct {
+	Field P
+}
+
+func testGenerics[P *T, T any](p P) {
+	// Calls to instantiated functions should not break checks.
+	slice := Zero[string]()
+	sort.Slice(slice, func(i, j int) bool {
+		return slice[i] < slice[j]
+	})
+
+	// Usage of instantiated fields should not break checks.
+	g := Inst[string]{"hello"}
+	g.Field = strings.TrimLeft(g.Field, "12234")
+
+	// Use of type parameters should not break checks.
+	var q P
+	p = q // SA4009: p is overwritten before its first use
+	q = &*p // SA4001: &* will be simplified
+}
+
+
+// FooErr should be called ErrFoo (ST1012)
+var FooErr error = errors.New("foo")
+`
+
+	WithOptions(EditorConfig{
+		Settings: map[string]interface{}{
+			"staticcheck": true,
+		},
+	}).Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("a/a.go")
+		env.Await(
+			env.DiagnosticAtRegexpFromSource("a/a.go", "sort.Slice", "sortslice"),
+			env.DiagnosticAtRegexpFromSource("a/a.go", "sort.Slice.(slice)", "SA1028"),
+			env.DiagnosticAtRegexpFromSource("a/a.go", "var (FooErr)", "ST1012"),
+			env.DiagnosticAtRegexpFromSource("a/a.go", `"12234"`, "SA1024"),
+			env.DiagnosticAtRegexpFromSource("a/a.go", "testGenerics.*(p P)", "SA4009"),
+			env.DiagnosticAtRegexpFromSource("a/a.go", "q = (&\\*p)", "SA4001"),
+		)
+	})
+}
diff --git a/internal/lsp/regtest/expectation.go b/internal/lsp/regtest/expectation.go
index 3540235..15de33f 100644
--- a/internal/lsp/regtest/expectation.go
+++ b/internal/lsp/regtest/expectation.go
@@ -465,6 +465,9 @@
 
 	// path is the scratch workdir-relative path to the file being asserted on.
 	path string
+
+	// optionally, the diagnostic source
+	source string
 }
 
 // Check implements the Expectation interface.
@@ -489,6 +492,9 @@
 				continue
 			}
 		}
+		if e.source != "" && e.source != d.Source {
+			continue
+		}
 		found = true
 		break
 	}
@@ -515,6 +521,9 @@
 	if e.message != "" {
 		desc += fmt.Sprintf(" with message %q", e.message)
 	}
+	if e.source != "" {
+		desc += fmt.Sprintf(" from source %q", e.source)
+	}
 	return desc
 }
 
@@ -619,6 +628,14 @@
 	return DiagnosticExpectation{path: name, pos: &pos, re: re, present: true, message: msg}
 }
 
+// DiagnosticAtRegexpFromSource expects a diagnostic at the first position
+// matching re, from the given source.
+func (e *Env) DiagnosticAtRegexpFromSource(name, re, source string) DiagnosticExpectation {
+	e.T.Helper()
+	pos := e.RegexpSearch(name, re)
+	return DiagnosticExpectation{path: name, pos: &pos, re: re, present: true, source: source}
+}
+
 // DiagnosticAt asserts that there is a diagnostic entry at the position
 // specified by line and col, for the workdir-relative path name.
 func DiagnosticAt(name string, line, col int) DiagnosticExpectation {