diff --git a/cmd/auth/go.mod b/cmd/auth/go.mod
new file mode 100644
index 00000000000..ea912ce7743
--- /dev/null
+++ b/cmd/auth/go.mod
@@ -0,0 +1,3 @@
+module golang.org/x/tools/cmd/auth
+
+go 1.23.0
diff --git a/go.mod b/go.mod
index 7e4e371b770..91de2267573 100644
--- a/go.mod
+++ b/go.mod
@@ -6,9 +6,9 @@ require (
github.com/google/go-cmp v0.6.0
github.com/yuin/goldmark v1.4.13
golang.org/x/mod v0.24.0
- golang.org/x/net v0.39.0
- golang.org/x/sync v0.13.0
+ golang.org/x/net v0.40.0
+ golang.org/x/sync v0.14.0
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457
)
-require golang.org/x/sys v0.32.0 // indirect
+require golang.org/x/sys v0.33.0 // indirect
diff --git a/go.sum b/go.sum
index ff5857bd93a..6a01512f3e4 100644
--- a/go.sum
+++ b/go.sum
@@ -4,11 +4,11 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
-golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
-golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
+golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
diff --git a/go/analysis/passes/gofix/doc.go b/go/analysis/passes/gofix/doc.go
new file mode 100644
index 00000000000..cb66e83fae1
--- /dev/null
+++ b/go/analysis/passes/gofix/doc.go
@@ -0,0 +1,50 @@
+// Copyright 2025 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 gofix defines an Analyzer that checks "//go:fix inline" directives.
+See golang.org/x/tools/internal/gofix/doc.go for details.
+
+# Analyzer gofixdirective
+
+gofixdirective: validate uses of gofix comment directives
+
+The gofixdirective analyzer checks "//go:fix inline" directives for correctness.
+
+The proposal https://go.dev/issue/32816 introduces the "//go:fix" directives.
+
+The analyzer checks for the following issues:
+
+- A constant definition can be marked for inlining only if it refers to another
+named constant.
+
+ //go:fix inline
+ const (
+ a = 1 // error
+ b = iota // error
+ c = a // OK
+ d = math.Pi // OK
+ )
+
+- A type definition can be marked for inlining only if it is an alias.
+
+ //go:fix inline
+ type (
+ T int // error
+ A = int // OK
+ )
+
+- An alias whose right-hand side contains a non-literal array size
+cannot be marked for inlining.
+
+ const two = 2
+
+ //go:fix inline
+ type (
+ A = []int // OK
+ B = [1]int // OK
+ C = [two]int // error
+ )
+*/
+package gofix
diff --git a/go/analysis/passes/gofix/gofix.go b/go/analysis/passes/gofix/gofix.go
new file mode 100644
index 00000000000..706e0759c3a
--- /dev/null
+++ b/go/analysis/passes/gofix/gofix.go
@@ -0,0 +1,34 @@
+// Copyright 2025 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 gofix defines an analyzer that checks go:fix directives.
+package gofix
+
+import (
+ _ "embed"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/astutil/cursor"
+ "golang.org/x/tools/internal/gofix/findgofix"
+)
+
+//go:embed doc.go
+var doc string
+
+var Analyzer = &analysis.Analyzer{
+ Name: "gofixdirective",
+ Doc: analysisinternal.MustExtractDoc(doc, "gofixdirective"),
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/gofix",
+ Run: run,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+}
+
+func run(pass *analysis.Pass) (any, error) {
+ root := cursor.Root(pass.ResultOf[inspect.Analyzer].(*inspector.Inspector))
+ findgofix.Find(pass, root, nil)
+ return nil, nil
+}
diff --git a/go/analysis/passes/gofix/gofix_test.go b/go/analysis/passes/gofix/gofix_test.go
new file mode 100644
index 00000000000..b2e6d4387d4
--- /dev/null
+++ b/go/analysis/passes/gofix/gofix_test.go
@@ -0,0 +1,17 @@
+// Copyright 2025 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 gofix_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "golang.org/x/tools/go/analysis/passes/gofix"
+)
+
+func Test(t *testing.T) {
+ testdata := analysistest.TestData()
+ analysistest.Run(t, testdata, gofix.Analyzer, "a")
+}
diff --git a/go/analysis/passes/gofix/testdata/src/a/a.go b/go/analysis/passes/gofix/testdata/src/a/a.go
new file mode 100644
index 00000000000..3588290cfb3
--- /dev/null
+++ b/go/analysis/passes/gofix/testdata/src/a/a.go
@@ -0,0 +1,47 @@
+// Copyright 2023 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.
+
+// This file contains tests for the gofix checker.
+
+package a
+
+const one = 1
+
+//go:fix inline
+const (
+ in3 = one
+ in4 = one
+ bad1 = 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
+)
+
+//go:fix inline
+const in5,
+ in6,
+ bad2 = one, one,
+ one + 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
+
+//go:fix inline
+const (
+ a = iota // want `invalid //go:fix inline directive: const value is iota`
+ b
+ in7 = one
+)
+
+func shadow() {
+ //go:fix inline
+ const a = iota // want `invalid //go:fix inline directive: const value is iota`
+
+ const iota = 2
+
+ //go:fix inline
+ const b = iota // not an error: iota is not the builtin
+}
+
+// Type aliases
+
+//go:fix inline
+type A int // want `invalid //go:fix inline directive: not a type alias`
+
+//go:fix inline
+type E = map[[one]string][]int // want `invalid //go:fix inline directive: array types not supported`
diff --git a/gopls/internal/analysis/hostport/hostport.go b/go/analysis/passes/hostport/hostport.go
similarity index 96%
rename from gopls/internal/analysis/hostport/hostport.go
rename to go/analysis/passes/hostport/hostport.go
index d95e475d1bf..e808b1aa1ba 100644
--- a/gopls/internal/analysis/hostport/hostport.go
+++ b/go/analysis/passes/hostport/hostport.go
@@ -15,7 +15,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/gopls/internal/util/safetoken"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
@@ -42,7 +41,7 @@ A similar diagnostic and fix are produced for a format string of "%s:%s".
var Analyzer = &analysis.Analyzer{
Name: "hostport",
Doc: Doc,
- URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/hostport",
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/hostport",
Requires: []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer},
Run: run,
}
@@ -121,7 +120,7 @@ func run(pass *analysis.Pass) (any, error) {
suffix := ""
if dialCall != nil {
suffix = fmt.Sprintf(" (passed to net.Dial at L%d)",
- safetoken.StartPosition(pass.Fset, dialCall.Pos()).Line)
+ pass.Fset.Position(dialCall.Pos()).Line)
}
pass.Report(analysis.Diagnostic{
diff --git a/gopls/internal/analysis/hostport/hostport_test.go b/go/analysis/passes/hostport/hostport_test.go
similarity index 87%
rename from gopls/internal/analysis/hostport/hostport_test.go
rename to go/analysis/passes/hostport/hostport_test.go
index 4e57a43e8d4..f3c18840fa0 100644
--- a/gopls/internal/analysis/hostport/hostport_test.go
+++ b/go/analysis/passes/hostport/hostport_test.go
@@ -8,7 +8,7 @@ import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
- "golang.org/x/tools/gopls/internal/analysis/hostport"
+ "golang.org/x/tools/go/analysis/passes/hostport"
)
func Test(t *testing.T) {
diff --git a/gopls/internal/analysis/hostport/main.go b/go/analysis/passes/hostport/main.go
similarity index 100%
rename from gopls/internal/analysis/hostport/main.go
rename to go/analysis/passes/hostport/main.go
diff --git a/gopls/internal/analysis/hostport/testdata/src/a/a.go b/go/analysis/passes/hostport/testdata/src/a/a.go
similarity index 100%
rename from gopls/internal/analysis/hostport/testdata/src/a/a.go
rename to go/analysis/passes/hostport/testdata/src/a/a.go
diff --git a/gopls/internal/analysis/hostport/testdata/src/a/a.go.golden b/go/analysis/passes/hostport/testdata/src/a/a.go.golden
similarity index 100%
rename from gopls/internal/analysis/hostport/testdata/src/a/a.go.golden
rename to go/analysis/passes/hostport/testdata/src/a/a.go.golden
diff --git a/go/analysis/passes/lostcancel/lostcancel.go b/go/analysis/passes/lostcancel/lostcancel.go
index a7fee180925..c0746789e9c 100644
--- a/go/analysis/passes/lostcancel/lostcancel.go
+++ b/go/analysis/passes/lostcancel/lostcancel.go
@@ -17,6 +17,7 @@ import (
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/astutil"
)
//go:embed doc.go
@@ -83,30 +84,22 @@ func runFunc(pass *analysis.Pass, node ast.Node) {
// {FuncDecl,FuncLit,CallExpr,SelectorExpr}.
// Find the set of cancel vars to analyze.
- stack := make([]ast.Node, 0, 32)
- ast.Inspect(node, func(n ast.Node) bool {
- switch n.(type) {
- case *ast.FuncLit:
- if len(stack) > 0 {
- return false // don't stray into nested functions
- }
- case nil:
- stack = stack[:len(stack)-1] // pop
- return true
+ astutil.PreorderStack(node, nil, func(n ast.Node, stack []ast.Node) bool {
+ if _, ok := n.(*ast.FuncLit); ok && len(stack) > 0 {
+ return false // don't stray into nested functions
}
- stack = append(stack, n) // push
- // Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]:
+ // Look for n=SelectorExpr beneath stack=[{AssignStmt,ValueSpec} CallExpr]:
//
// ctx, cancel := context.WithCancel(...)
// ctx, cancel = context.WithCancel(...)
// var ctx, cancel = context.WithCancel(...)
//
- if !isContextWithCancel(pass.TypesInfo, n) || !isCall(stack[len(stack)-2]) {
+ if !isContextWithCancel(pass.TypesInfo, n) || !isCall(stack[len(stack)-1]) {
return true
}
var id *ast.Ident // id of cancel var
- stmt := stack[len(stack)-3]
+ stmt := stack[len(stack)-2]
switch stmt := stmt.(type) {
case *ast.ValueSpec:
if len(stmt.Names) > 1 {
diff --git a/go/analysis/passes/structtag/structtag.go b/go/analysis/passes/structtag/structtag.go
index da4afd1b232..13a9997316e 100644
--- a/go/analysis/passes/structtag/structtag.go
+++ b/go/analysis/passes/structtag/structtag.go
@@ -89,8 +89,7 @@ var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
// checkCanonicalFieldTag checks a single struct field tag.
func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) {
- switch pass.Pkg.Path() {
- case "encoding/json", "encoding/json/v2", "encoding/xml":
+ if strings.HasPrefix(pass.Pkg.Path(), "encoding/") {
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return
diff --git a/go/analysis/passes/testinggoroutine/testinggoroutine.go b/go/analysis/passes/testinggoroutine/testinggoroutine.go
index f49ac4eb1a0..360ba0e74d8 100644
--- a/go/analysis/passes/testinggoroutine/testinggoroutine.go
+++ b/go/analysis/passes/testinggoroutine/testinggoroutine.go
@@ -17,6 +17,7 @@ import (
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@@ -186,7 +187,7 @@ func goAsyncCall(info *types.Info, goStmt *ast.GoStmt, toDecl func(*types.Func)
call := goStmt.Call
fun := ast.Unparen(call.Fun)
- if id := funcIdent(fun); id != nil {
+ if id := typesinternal.UsedIdent(info, fun); id != nil {
if lit := funcLitInScope(id); lit != nil {
return &asyncCall{region: lit, async: goStmt, scope: nil, fun: fun}
}
@@ -217,7 +218,7 @@ func tRunAsyncCall(info *types.Info, call *ast.CallExpr) *asyncCall {
return &asyncCall{region: lit, async: call, scope: lit, fun: fun}
}
- if id := funcIdent(fun); id != nil {
+ if id := typesinternal.UsedIdent(info, fun); id != nil {
if lit := funcLitInScope(id); lit != nil { // function lit in variable?
return &asyncCall{region: lit, async: call, scope: lit, fun: fun}
}
diff --git a/go/analysis/passes/testinggoroutine/util.go b/go/analysis/passes/testinggoroutine/util.go
index 88e77fb4fc4..db2e5f76d14 100644
--- a/go/analysis/passes/testinggoroutine/util.go
+++ b/go/analysis/passes/testinggoroutine/util.go
@@ -8,8 +8,6 @@ import (
"go/ast"
"go/types"
"slices"
-
- "golang.org/x/tools/internal/typeparams"
)
// AST and types utilities that not specific to testinggoroutines.
@@ -52,19 +50,6 @@ func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
return slices.Contains(names, f.Name())
}
-func funcIdent(fun ast.Expr) *ast.Ident {
- switch fun := ast.Unparen(fun).(type) {
- case *ast.IndexExpr, *ast.IndexListExpr:
- x, _, _, _ := typeparams.UnpackIndexExpr(fun) // necessary?
- id, _ := x.(*ast.Ident)
- return id
- case *ast.Ident:
- return fun
- default:
- return nil
- }
-}
-
// funcLitInScope returns a FuncLit that id is at least initially assigned to.
//
// TODO: This is closely tied to id.Obj which is deprecated.
diff --git a/go/analysis/passes/unreachable/unreachable.go b/go/analysis/passes/unreachable/unreachable.go
index fcf5fbd9060..317f034992b 100644
--- a/go/analysis/passes/unreachable/unreachable.go
+++ b/go/analysis/passes/unreachable/unreachable.go
@@ -188,6 +188,9 @@ func (d *deadState) findDead(stmt ast.Stmt) {
case *ast.EmptyStmt:
// do not warn about unreachable empty statements
default:
+ // (This call to pass.Report is a frequent source
+ // of diagnostics beyond EOF in a truncated file;
+ // see #71659.)
d.pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
End: stmt.End(),
diff --git a/go/analysis/unitchecker/vet_std_test.go b/go/analysis/unitchecker/vet_std_test.go
index a79224c7188..a761bc02f31 100644
--- a/go/analysis/unitchecker/vet_std_test.go
+++ b/go/analysis/unitchecker/vet_std_test.go
@@ -5,6 +5,7 @@
package unitchecker_test
import (
+ "go/version"
"os"
"os/exec"
"runtime"
@@ -24,6 +25,8 @@ import (
"golang.org/x/tools/go/analysis/passes/directive"
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/passes/framepointer"
+ "golang.org/x/tools/go/analysis/passes/gofix"
+ "golang.org/x/tools/go/analysis/passes/hostport"
"golang.org/x/tools/go/analysis/passes/httpresponse"
"golang.org/x/tools/go/analysis/passes/ifaceassert"
"golang.org/x/tools/go/analysis/passes/loopclosure"
@@ -62,7 +65,9 @@ func vet() {
directive.Analyzer,
errorsas.Analyzer,
framepointer.Analyzer,
+ gofix.Analyzer,
httpresponse.Analyzer,
+ hostport.Analyzer,
ifaceassert.Analyzer,
loopclosure.Analyzer,
lostcancel.Analyzer,
@@ -91,8 +96,14 @@ func TestVetStdlib(t *testing.T) {
if testing.Short() {
t.Skip("skipping in -short mode")
}
- if version := runtime.Version(); !strings.HasPrefix(version, "devel") {
- t.Skipf("This test is only wanted on development branches where code can be easily fixed. Skipping because runtime.Version=%q.", version)
+ if builder := os.Getenv("GO_BUILDER_NAME"); builder != "" && !strings.HasPrefix(builder, "x_tools-gotip-") {
+ // Run on builders like x_tools-gotip-linux-amd64-longtest,
+ // skip on others like x_tools-go1.24-linux-amd64-longtest.
+ t.Skipf("This test is only wanted on development branches where code can be easily fixed. Skipping on non-gotip builder %q.", builder)
+ } else if v := runtime.Version(); !strings.Contains(v, "devel") || version.Compare(v, version.Lang(v)) != 0 {
+ // Run on versions like "go1.25-devel_9ce47e66e8 Wed Mar 26 03:48:50 2025 -0700",
+ // skip on others like "go1.24.2" or "go1.24.2-devel_[…]".
+ t.Skipf("This test is only wanted on development versions where code can be easily fixed. Skipping on non-gotip version %q.", v)
}
cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "std")
diff --git a/go/ast/inspector/inspector.go b/go/ast/inspector/inspector.go
index 1da4a361f0b..674490a65b4 100644
--- a/go/ast/inspector/inspector.go
+++ b/go/ast/inspector/inspector.go
@@ -48,10 +48,10 @@ type Inspector struct {
events []event
}
-//go:linkname events
+//go:linkname events golang.org/x/tools/go/ast/inspector.events
func events(in *Inspector) []event { return in.events }
-//go:linkname packEdgeKindAndIndex
+//go:linkname packEdgeKindAndIndex golang.org/x/tools/go/ast/inspector.packEdgeKindAndIndex
func packEdgeKindAndIndex(ek edge.Kind, index int) int32 {
return int32(uint32(index+1)<<7 | uint32(ek))
}
@@ -59,7 +59,7 @@ func packEdgeKindAndIndex(ek edge.Kind, index int) int32 {
// unpackEdgeKindAndIndex unpacks the edge kind and edge index (within
// an []ast.Node slice) from the parent field of a pop event.
//
-//go:linkname unpackEdgeKindAndIndex
+//go:linkname unpackEdgeKindAndIndex golang.org/x/tools/go/ast/inspector.unpackEdgeKindAndIndex
func unpackEdgeKindAndIndex(x int32) (edge.Kind, int) {
// The "parent" field of a pop node holds the
// edge Kind in the lower 7 bits and the index+1
diff --git a/go/ast/inspector/typeof.go b/go/ast/inspector/typeof.go
index 97784484578..e936c67c985 100644
--- a/go/ast/inspector/typeof.go
+++ b/go/ast/inspector/typeof.go
@@ -217,7 +217,7 @@ func typeOf(n ast.Node) uint64 {
return 0
}
-//go:linkname maskOf
+//go:linkname maskOf golang.org/x/tools/go/ast/inspector.maskOf
func maskOf(nodes []ast.Node) uint64 {
if len(nodes) == 0 {
return math.MaxUint64 // match all node types
diff --git a/go/packages/golist.go b/go/packages/golist.go
index 0458b4f9c43..96e43cd8093 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -851,8 +851,6 @@ func (state *golistState) cfgInvocation() gocommand.Invocation {
cfg := state.cfg
return gocommand.Invocation{
BuildFlags: cfg.BuildFlags,
- ModFile: cfg.modFile,
- ModFlag: cfg.modFlag,
CleanEnv: cfg.Env != nil,
Env: cfg.Env,
Logf: cfg.Logf,
diff --git a/go/packages/packages.go b/go/packages/packages.go
index 6665a04c173..060ab08efbc 100644
--- a/go/packages/packages.go
+++ b/go/packages/packages.go
@@ -229,14 +229,6 @@ type Config struct {
// consistent package metadata about unsaved files. However,
// drivers may vary in their level of support for overlays.
Overlay map[string][]byte
-
- // -- Hidden configuration fields only for use in x/tools --
-
- // modFile will be used for -modfile in go command invocations.
- modFile string
-
- // modFlag will be used for -modfile in go command invocations.
- modFlag string
}
// Load loads and returns the Go packages named by the given patterns.
@@ -569,12 +561,6 @@ func init() {
packagesinternal.GetDepsErrors = func(p any) []*packagesinternal.PackageError {
return p.(*Package).depsErrors
}
- packagesinternal.SetModFile = func(config any, value string) {
- config.(*Config).modFile = value
- }
- packagesinternal.SetModFlag = func(config any, value string) {
- config.(*Config).modFlag = value
- }
packagesinternal.TypecheckCgo = int(typecheckCgo)
packagesinternal.DepsErrors = int(needInternalDepsErrors)
}
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index ae3cbb6bb2b..2623aa5a03b 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -28,7 +28,9 @@ import (
"time"
"github.com/google/go-cmp/cmp"
+ "golang.org/x/sync/errgroup"
"golang.org/x/tools/go/packages"
+ "golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/packagesinternal"
"golang.org/x/tools/internal/packagestest"
"golang.org/x/tools/internal/testenv"
@@ -3400,3 +3402,89 @@ func writeTree(t *testing.T, archive string) string {
}
return root
}
+
+// This is not a test of go/packages at all: it's a test of whether it
+// is possible to delete the directory used by go list once it has
+// finished. It is intended to evaluate the hypothesis (to explain
+// issue #71544) that the go command, on Windows, occasionally fails
+// to release all its handles to the temporary directory even when it
+// should have finished.
+//
+// If this test ever fails, the combination of the gocommand package
+// and the go command itself has a bug.
+func TestRmdirAfterGoList_Runner(t *testing.T) {
+ t.Skip("golang/go#73503: this test is frequently flaky")
+
+ testRmdirAfterGoList(t, func(ctx context.Context, dir string) {
+ var runner gocommand.Runner
+ stdout, stderr, friendlyErr, err := runner.RunRaw(ctx, gocommand.Invocation{
+ Verb: "list",
+ Args: []string{"-json", "example.com/p"},
+ WorkingDir: dir,
+ })
+ if ctx.Err() != nil {
+ return // don't report error if canceled
+ }
+ if err != nil || friendlyErr != nil {
+ t.Fatalf("go list failed: %v, %v (stdout=%s stderr=%s)",
+ err, friendlyErr, stdout, stderr)
+ }
+ })
+}
+
+// TestRmdirAfterGoList_Direct is a variant of
+// TestRmdirAfterGoList_Runner that executes go list directly, to
+// control for the substantial logic of the gocommand package.
+//
+// If this test ever fails, the go command itself has a bug.
+func TestRmdirAfterGoList_Direct(t *testing.T) {
+ testRmdirAfterGoList(t, func(ctx context.Context, dir string) {
+ cmd := exec.Command("go", "list", "-json", "example.com/p")
+ cmd.Dir = dir
+ cmd.Stdout = new(strings.Builder)
+ cmd.Stderr = new(strings.Builder)
+ err := cmd.Run()
+ if ctx.Err() != nil {
+ return // don't report error if canceled
+ }
+ if err != nil {
+ t.Fatalf("go list failed: %v (stdout=%s stderr=%s)",
+ err, cmd.Stdout, cmd.Stderr)
+ }
+ })
+}
+
+func testRmdirAfterGoList(t *testing.T, f func(ctx context.Context, dir string)) {
+ testenv.NeedsExec(t)
+
+ dir := t.TempDir()
+ if err := os.Mkdir(filepath.Join(dir, "p"), 0777); err != nil {
+ t.Fatalf("mkdir p: %v", err)
+ }
+
+ // Create a go.mod file and 100 trivial Go files for the go command to read.
+ if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com"), 0666); err != nil {
+ t.Fatal(err)
+ }
+ for i := range 100 {
+ filename := filepath.Join(dir, fmt.Sprintf("p/%d.go", i))
+ if err := os.WriteFile(filename, []byte("package p"), 0666); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ g, ctx := errgroup.WithContext(context.Background())
+ for range 10 {
+ g.Go(func() error {
+ f(ctx, dir)
+ // Return an error so that concurrent invocations are canceled.
+ return fmt.Errorf("oops")
+ })
+ }
+ g.Wait() // ignore expected error
+
+ // This is the critical operation.
+ if err := os.RemoveAll(dir); err != nil {
+ t.Fatalf("failed to remove temp dir: %v", err)
+ }
+}
diff --git a/go/types/internal/play/play.go b/go/types/internal/play/play.go
index 4212a6b82cf..77a90502135 100644
--- a/go/types/internal/play/play.go
+++ b/go/types/internal/play/play.go
@@ -26,6 +26,7 @@ import (
"os"
"path/filepath"
"reflect"
+ "slices"
"strconv"
"strings"
@@ -167,8 +168,9 @@ func handleSelectJSON(w http.ResponseWriter, req *http.Request) {
// It's usually the same, but may differ in edge
// cases (e.g. around FuncType.Func).
inspect := inspector.New([]*ast.File{file})
- if cur, ok := cursor.Root(inspect).FindPos(startPos, endPos); ok {
- fmt.Fprintf(out, "Cursor.FindPos().Stack() = %v\n", cur.Stack(nil))
+ if cur, ok := cursor.Root(inspect).FindByPos(startPos, endPos); ok {
+ fmt.Fprintf(out, "Cursor.FindPos().Enclosing() = %v\n",
+ slices.Collect(cur.Enclosing()))
} else {
fmt.Fprintf(out, "Cursor.FindPos() failed\n")
}
diff --git a/go/types/typeutil/callee.go b/go/types/typeutil/callee.go
index 53b71339305..5f10f56cbaf 100644
--- a/go/types/typeutil/callee.go
+++ b/go/types/typeutil/callee.go
@@ -48,7 +48,7 @@ func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
// This function should live in typesinternal, but cannot because it would
// create an import cycle.
//
-//go:linkname usedIdent
+//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent
func usedIdent(info *types.Info, e ast.Expr) *ast.Ident {
if info.Types == nil || info.Uses == nil {
panic("one of info.Types or info.Uses is nil; both must be populated")
@@ -78,7 +78,7 @@ func usedIdent(info *types.Info, e ast.Expr) *ast.Ident {
// interfaceMethod reports whether its argument is a method of an interface.
// This function should live in typesinternal, but cannot because it would create an import cycle.
//
-//go:linkname interfaceMethod
+//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod
func interfaceMethod(f *types.Func) bool {
recv := f.Signature().Recv()
return recv != nil && types.IsInterface(recv.Type())
diff --git a/gopls/README.md b/gopls/README.md
index 6602e0c27a7..e17184e0d51 100644
--- a/gopls/README.md
+++ b/gopls/README.md
@@ -20,8 +20,10 @@ supported in each client editor.
To get started with `gopls`, install an LSP plugin in your editor of choice.
+
* [VS Code](https://github.com/golang/vscode-go/blob/master/README.md)
* [Vim / Neovim](doc/vim.md)
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 4b2bff1a63a..e18a7c7efda 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -22,15 +22,3207 @@ which aggregates analyzers from a variety of sources:
To enable or disable analyzers, use the [analyses](settings.md#analyses) setting.
-In addition, gopls includes the [`staticcheck` suite](https://staticcheck.dev/docs/checks),
-though these analyzers are off by default.
-Use the [`staticcheck`](settings.md#staticcheck`) setting to enable them,
-and consult staticcheck's documentation for analyzer details.
+In addition, gopls includes the [`staticcheck` suite](https://staticcheck.dev/docs/checks).
+When the [`staticcheck`](settings.md#staticcheck`) boolean option is
+unset, slightly more than half of these analyzers are enabled by
+default; this subset has been chosen for precision and efficiency. Set
+`staticcheck` to `true` to enable the complete set, or to `false` to
+disable the complete set.
-
+Staticcheck analyzers, like all other analyzers, can be explicitly
+enabled or disabled using the `analyzers` configuration setting; this
+setting takes precedence over the `staticcheck` setting, so,
+regardless of what value of `staticcheck` you use (true/false/unset),
+you can make adjustments to your preferred set of analyzers.
+
+## `QF1001`: Apply De Morgan's law
+
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"QF1001": true}`.
+
+Package documentation: [QF1001](https://staticcheck.dev/docs/checks/#QF1001)
+
+
+## `QF1002`: Convert untagged switch to tagged switch
+
+
+An untagged switch that compares a single variable against a series of
+values can be replaced with a tagged switch.
+
+Before:
+
+ switch {
+ case x == 1 || x == 2, x == 3:
+ ...
+ case x == 4:
+ ...
+ default:
+ ...
+ }
+
+After:
+
+ switch x {
+ case 1, 2, 3:
+ ...
+ case 4:
+ ...
+ default:
+ ...
+ }
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [QF1002](https://staticcheck.dev/docs/checks/#QF1002)
+
+
+## `QF1003`: Convert if/else-if chain to tagged switch
+
+
+A series of if/else-if checks comparing the same variable against
+values can be replaced with a tagged switch.
+
+Before:
+
+ if x == 1 || x == 2 {
+ ...
+ } else if x == 3 {
+ ...
+ } else {
+ ...
+ }
+
+After:
+
+ switch x {
+ case 1, 2:
+ ...
+ case 3:
+ ...
+ default:
+ ...
+ }
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [QF1003](https://staticcheck.dev/docs/checks/#QF1003)
+
+
+## `QF1004`: Use strings.ReplaceAll instead of strings.Replace with n == -1
+
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [QF1004](https://staticcheck.dev/docs/checks/#QF1004)
+
+
+## `QF1005`: Expand call to math.Pow
+
+
+Some uses of math.Pow can be simplified to basic multiplication.
+
+Before:
+
+ math.Pow(x, 2)
+
+After:
+
+ x * x
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"QF1005": true}`.
+
+Package documentation: [QF1005](https://staticcheck.dev/docs/checks/#QF1005)
+
+
+## `QF1006`: Lift if+break into loop condition
+
+
+Before:
+
+ for {
+ if done {
+ break
+ }
+ ...
+ }
+
+After:
+
+ for !done {
+ ...
+ }
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"QF1006": true}`.
+
+Package documentation: [QF1006](https://staticcheck.dev/docs/checks/#QF1006)
+
+
+## `QF1007`: Merge conditional assignment into variable declaration
+
+
+Before:
+
+ x := false
+ if someCondition {
+ x = true
+ }
+
+After:
+
+ x := someCondition
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"QF1007": true}`.
+
+Package documentation: [QF1007](https://staticcheck.dev/docs/checks/#QF1007)
+
+
+## `QF1008`: Omit embedded fields from selector expression
+
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"QF1008": true}`.
+
+Package documentation: [QF1008](https://staticcheck.dev/docs/checks/#QF1008)
+
+
+## `QF1009`: Use time.Time.Equal instead of == operator
+
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [QF1009](https://staticcheck.dev/docs/checks/#QF1009)
+
+
+## `QF1010`: Convert slice of bytes to string when printing it
+
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [QF1010](https://staticcheck.dev/docs/checks/#QF1010)
+
+
+## `QF1011`: Omit redundant type from variable declaration
+
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"QF1011": true}`.
+
+Package documentation: [QF1011](https://staticcheck.dev/docs/checks/#)
+
+
+## `QF1012`: Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))
+
+
+Available since
+ 2022.1
+
+
+Default: on.
+
+Package documentation: [QF1012](https://staticcheck.dev/docs/checks/#QF1012)
+
+
+## `S1000`: Use plain channel send or receive instead of single-case select
+
+
+Select statements with a single case can be replaced with a simple
+send or receive.
+
+Before:
+
+ select {
+ case x := <-ch:
+ fmt.Println(x)
+ }
+
+After:
+
+ x := <-ch
+ fmt.Println(x)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1000](https://staticcheck.dev/docs/checks/#S1000)
+
+
+## `S1001`: Replace for loop with call to copy
+
+
+Use copy() for copying elements from one slice to another. For
+arrays of identical size, you can use simple assignment.
+
+Before:
+
+ for i, x := range src {
+ dst[i] = x
+ }
+
+After:
+
+ copy(dst, src)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1001](https://staticcheck.dev/docs/checks/#S1001)
+
+
+## `S1002`: Omit comparison with boolean constant
+
+
+Before:
+
+ if x == true {}
+
+After:
+
+ if x {}
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1002": true}`.
+
+Package documentation: [S1002](https://staticcheck.dev/docs/checks/#S1002)
+
+
+## `S1003`: Replace call to strings.Index with strings.Contains
+
+
+Before:
+
+ if strings.Index(x, y) != -1 {}
+
+After:
+
+ if strings.Contains(x, y) {}
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1003](https://staticcheck.dev/docs/checks/#S1003)
+
+
+## `S1004`: Replace call to bytes.Compare with bytes.Equal
+
+
+Before:
+
+ if bytes.Compare(x, y) == 0 {}
+
+After:
+
+ if bytes.Equal(x, y) {}
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1004](https://staticcheck.dev/docs/checks/#S1004)
+
+
+## `S1005`: Drop unnecessary use of the blank identifier
+
+
+In many cases, assigning to the blank identifier is unnecessary.
+
+Before:
+
+ for _ = range s {}
+ x, _ = someMap[key]
+ _ = <-ch
+
+After:
+
+ for range s{}
+ x = someMap[key]
+ <-ch
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1005": true}`.
+
+Package documentation: [S1005](https://staticcheck.dev/docs/checks/#S1005)
+
+
+## `S1006`: Use 'for { ... }' for infinite loops
+
+
+For infinite loops, using for { ... } is the most idiomatic choice.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1006": true}`.
+
+Package documentation: [S1006](https://staticcheck.dev/docs/checks/#S1006)
+
+
+## `S1007`: Simplify regular expression by using raw string literal
+
+
+Raw string literals use backticks instead of quotation marks and do not support
+any escape sequences. This means that the backslash can be used
+freely, without the need of escaping.
+
+Since regular expressions have their own escape sequences, raw strings
+can improve their readability.
+
+Before:
+
+ regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
+
+After:
+
+ regexp.Compile(`\A(\w+) profile: total \d+\n\z`)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1007](https://staticcheck.dev/docs/checks/#S1007)
+
+
+## `S1008`: Simplify returning boolean expression
+
+
+Before:
+
+ if {
+ return true
+ }
+ return false
+
+After:
+
+ return
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1008": true}`.
+
+Package documentation: [S1008](https://staticcheck.dev/docs/checks/#S1008)
+
+
+## `S1009`: Omit redundant nil check on slices, maps, and channels
+
+
+The len function is defined for all slices, maps, and
+channels, even nil ones, which have a length of zero. It is not necessary to
+check for nil before checking that their length is not zero.
+
+Before:
+
+ if x != nil && len(x) != 0 {}
+
+After:
+
+ if len(x) != 0 {}
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1009](https://staticcheck.dev/docs/checks/#S1009)
+
+
+## `S1010`: Omit default slice index
+
+
+When slicing, the second index defaults to the length of the value,
+making s[n:len(s)] and s[n:] equivalent.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1010](https://staticcheck.dev/docs/checks/#S1010)
+
+
+## `S1011`: Use a single append to concatenate two slices
+
+
+Before:
+
+ for _, e := range y {
+ x = append(x, e)
+ }
+
+ for i := range y {
+ x = append(x, y[i])
+ }
+
+ for i := range y {
+ v := y[i]
+ x = append(x, v)
+ }
+
+After:
+
+ x = append(x, y...)
+ x = append(x, y...)
+ x = append(x, y...)
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1011": true}`.
+
+Package documentation: [S1011](https://staticcheck.dev/docs/checks/#S1011)
+
+
+## `S1012`: Replace time.Now().Sub(x) with time.Since(x)
+
+
+The time.Since helper has the same effect as using time.Now().Sub(x)
+but is easier to read.
+
+Before:
+
+ time.Now().Sub(x)
+
+After:
+
+ time.Since(x)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1012](https://staticcheck.dev/docs/checks/#S1012)
+
+
+## `S1016`: Use a type conversion instead of manually copying struct fields
+
+
+Two struct types with identical fields can be converted between each
+other. In older versions of Go, the fields had to have identical
+struct tags. Since Go 1.8, however, struct tags are ignored during
+conversions. It is thus not necessary to manually copy every field
+individually.
+
+Before:
+
+ var x T1
+ y := T2{
+ Field1: x.Field1,
+ Field2: x.Field2,
+ }
+
+After:
+
+ var x T1
+ y := T2(x)
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1016": true}`.
+
+Package documentation: [S1016](https://staticcheck.dev/docs/checks/#S1016)
+
+
+## `S1017`: Replace manual trimming with strings.TrimPrefix
+
+
+Instead of using strings.HasPrefix and manual slicing, use the
+strings.TrimPrefix function. If the string doesn't start with the
+prefix, the original string will be returned. Using strings.TrimPrefix
+reduces complexity, and avoids common bugs, such as off-by-one
+mistakes.
+
+Before:
+
+ if strings.HasPrefix(str, prefix) {
+ str = str[len(prefix):]
+ }
+
+After:
+
+ str = strings.TrimPrefix(str, prefix)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1017](https://staticcheck.dev/docs/checks/#S1017)
+
+
+## `S1018`: Use 'copy' for sliding elements
+
+
+copy() permits using the same source and destination slice, even with
+overlapping ranges. This makes it ideal for sliding elements in a
+slice.
+
+Before:
+
+ for i := 0; i < n; i++ {
+ bs[i] = bs[offset+i]
+ }
+
+After:
+
+ copy(bs[:n], bs[offset:])
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1018](https://staticcheck.dev/docs/checks/#S1018)
+
+
+## `S1019`: Simplify 'make' call by omitting redundant arguments
+
+
+The 'make' function has default values for the length and capacity
+arguments. For channels, the length defaults to zero, and for slices,
+the capacity defaults to the length.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1019](https://staticcheck.dev/docs/checks/#S1019)
+
+
+## `S1020`: Omit redundant nil check in type assertion
+
+
+Before:
+
+ if _, ok := i.(T); ok && i != nil {}
+
+After:
+
+ if _, ok := i.(T); ok {}
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1020](https://staticcheck.dev/docs/checks/#S1020)
+
+
+## `S1021`: Merge variable declaration and assignment
+
+
+Before:
+
+ var x uint
+ x = 1
+
+After:
+
+ var x uint = 1
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1021": true}`.
+
+Package documentation: [S1021](https://staticcheck.dev/docs/checks/#S1021)
+
+
+## `S1023`: Omit redundant control flow
+
+
+Functions that have no return value do not need a return statement as
+the final statement of the function.
+
+Switches in Go do not have automatic fallthrough, unlike languages
+like C. It is not necessary to have a break statement as the final
+statement in a case block.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1023](https://staticcheck.dev/docs/checks/#S1023)
+
+
+## `S1024`: Replace x.Sub(time.Now()) with time.Until(x)
+
+
+The time.Until helper has the same effect as using x.Sub(time.Now())
+but is easier to read.
+
+Before:
+
+ x.Sub(time.Now())
+
+After:
+
+ time.Until(x)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1024](https://staticcheck.dev/docs/checks/#S1024)
+
+
+## `S1025`: Don't use fmt.Sprintf("%s", x) unnecessarily
+
+
+In many instances, there are easier and more efficient ways of getting
+a value's string representation. Whenever a value's underlying type is
+a string already, or the type has a String method, they should be used
+directly.
+
+Given the following shared definitions
+
+ type T1 string
+ type T2 int
+
+ func (T2) String() string { return "Hello, world" }
+
+ var x string
+ var y T1
+ var z T2
+
+we can simplify
+
+ fmt.Sprintf("%s", x)
+ fmt.Sprintf("%s", y)
+ fmt.Sprintf("%s", z)
+
+to
+
+ x
+ string(y)
+ z.String()
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1025": true}`.
+
+Package documentation: [S1025](https://staticcheck.dev/docs/checks/#S1025)
+
+
+## `S1028`: Simplify error construction with fmt.Errorf
+
+
+Before:
+
+ errors.New(fmt.Sprintf(...))
+
+After:
+
+ fmt.Errorf(...)
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1028](https://staticcheck.dev/docs/checks/#S1028)
+
+
+## `S1029`: Range over the string directly
+
+
+Ranging over a string will yield byte offsets and runes. If the offset
+isn't used, this is functionally equivalent to converting the string
+to a slice of runes and ranging over that. Ranging directly over the
+string will be more performant, however, as it avoids allocating a new
+slice, the size of which depends on the length of the string.
+
+Before:
+
+ for _, r := range []rune(s) {}
+
+After:
+
+ for _, r := range s {}
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"S1029": true}`.
+
+Package documentation: [S1029](https://staticcheck.dev/docs/checks/#S1029)
+
+
+## `S1030`: Use bytes.Buffer.String or bytes.Buffer.Bytes
+
+
+bytes.Buffer has both a String and a Bytes method. It is almost never
+necessary to use string(buf.Bytes()) or []byte(buf.String()) – simply
+use the other method.
+
+The only exception to this are map lookups. Due to a compiler optimization,
+m[string(buf.Bytes())] is more efficient than m[buf.String()].
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1030](https://staticcheck.dev/docs/checks/#S1030)
+
+
+## `S1031`: Omit redundant nil check around loop
+
+
+You can use range on nil slices and maps, the loop will simply never
+execute. This makes an additional nil check around the loop
+unnecessary.
+
+Before:
+
+ if s != nil {
+ for _, x := range s {
+ ...
+ }
+ }
+
+After:
+
+ for _, x := range s {
+ ...
+ }
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [S1031](https://staticcheck.dev/docs/checks/#S1031)
+
+
+## `S1032`: Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)
+
+
+The sort.Ints, sort.Float64s and sort.Strings functions are easier to
+read than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))
+and sort.Sort(sort.StringSlice(x)).
+
+Before:
+
+ sort.Sort(sort.StringSlice(x))
+
+After:
+
+ sort.Strings(x)
+
+Available since
+ 2019.1
+
+
+Default: on.
+
+Package documentation: [S1032](https://staticcheck.dev/docs/checks/#S1032)
+
+
+## `S1033`: Unnecessary guard around call to 'delete'
+
+
+Calling delete on a nil map is a no-op.
+
+Available since
+ 2019.2
+
+
+Default: on.
+
+Package documentation: [S1033](https://staticcheck.dev/docs/checks/#S1033)
+
+
+## `S1034`: Use result of type assertion to simplify cases
+
+
+Available since
+ 2019.2
+
+
+Default: on.
+
+Package documentation: [S1034](https://staticcheck.dev/docs/checks/#S1034)
+
+
+## `S1035`: Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header
+
+
+The methods on net/http.Header, namely Add, Del, Get
+and Set, already canonicalize the given header name.
+
+Available since
+ 2020.1
+
+
+Default: on.
+
+Package documentation: [S1035](https://staticcheck.dev/docs/checks/#S1035)
+
+
+## `S1036`: Unnecessary guard around map access
+
+
+When accessing a map key that doesn't exist yet, one receives a zero
+value. Often, the zero value is a suitable value, for example when
+using append or doing integer math.
+
+The following
+
+ if _, ok := m["foo"]; ok {
+ m["foo"] = append(m["foo"], "bar")
+ } else {
+ m["foo"] = []string{"bar"}
+ }
+
+can be simplified to
+
+ m["foo"] = append(m["foo"], "bar")
+
+and
+
+ if _, ok := m2["k"]; ok {
+ m2["k"] += 4
+ } else {
+ m2["k"] = 4
+ }
+
+can be simplified to
+
+ m["k"] += 4
+
+Available since
+ 2020.1
+
+
+Default: on.
+
+Package documentation: [S1036](https://staticcheck.dev/docs/checks/#S1036)
+
+
+## `S1037`: Elaborate way of sleeping
+
+
+Using a select statement with a single case receiving
+from the result of time.After is a very elaborate way of sleeping that
+can much simpler be expressed with a simple call to time.Sleep.
+
+Available since
+ 2020.1
+
+
+Default: on.
+
+Package documentation: [S1037](https://staticcheck.dev/docs/checks/#S1037)
+
+
+## `S1038`: Unnecessarily complex way of printing formatted string
+
+
+Instead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).
+
+Available since
+ 2020.1
+
+
+Default: on.
+
+Package documentation: [S1038](https://staticcheck.dev/docs/checks/#S1038)
+
+
+## `S1039`: Unnecessary use of fmt.Sprint
+
+
+Calling fmt.Sprint with a single string argument is unnecessary
+and identical to using the string directly.
+
+Available since
+ 2020.1
+
+
+Default: on.
+
+Package documentation: [S1039](https://staticcheck.dev/docs/checks/#S1039)
+
+
+## `S1040`: Type assertion to current type
+
+
+The type assertion x.(SomeInterface), when x already has type
+SomeInterface, can only fail if x is nil. Usually, this is
+left-over code from when x had a different type and you can safely
+delete the type assertion. If you want to check that x is not nil,
+consider being explicit and using an actual if x == nil comparison
+instead of relying on the type assertion panicking.
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [S1040](https://staticcheck.dev/docs/checks/#S1040)
+
+
+## `SA1000`: Invalid regular expression
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1000": true}`.
+
+Package documentation: [SA1000](https://staticcheck.dev/docs/checks/#SA1000)
+
+
+## `SA1001`: Invalid template
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1001](https://staticcheck.dev/docs/checks/#SA1001)
+
+
+## `SA1002`: Invalid format in time.Parse
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1002": true}`.
+
+Package documentation: [SA1002](https://staticcheck.dev/docs/checks/#SA1002)
+
+
+## `SA1003`: Unsupported argument to functions in encoding/binary
+
+
+The encoding/binary package can only serialize types with known sizes.
+This precludes the use of the int and uint types, as their sizes
+differ on different architectures. Furthermore, it doesn't support
+serializing maps, channels, strings, or functions.
+
+Before Go 1.8, bool wasn't supported, either.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1003": true}`.
+
+Package documentation: [SA1003](https://staticcheck.dev/docs/checks/#SA1003)
+
+
+## `SA1004`: Suspiciously small untyped constant in time.Sleep
+
+
+The time.Sleep function takes a time.Duration as its only argument.
+Durations are expressed in nanoseconds. Thus, calling time.Sleep(1)
+will sleep for 1 nanosecond. This is a common source of bugs, as sleep
+functions in other languages often accept seconds or milliseconds.
+
+The time package provides constants such as time.Second to express
+large durations. These can be combined with arithmetic to express
+arbitrary durations, for example 5 * time.Second for 5 seconds.
+
+If you truly meant to sleep for a tiny amount of time, use
+n * time.Nanosecond to signal to Staticcheck that you did mean to sleep
+for some amount of nanoseconds.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1004](https://staticcheck.dev/docs/checks/#SA1004)
+
+
+## `SA1005`: Invalid first argument to exec.Command
+
+
+os/exec runs programs directly (using variants of the fork and exec
+system calls on Unix systems). This shouldn't be confused with running
+a command in a shell. The shell will allow for features such as input
+redirection, pipes, and general scripting. The shell is also
+responsible for splitting the user's input into a program name and its
+arguments. For example, the equivalent to
+
+ ls / /tmp
+
+would be
+
+ exec.Command("ls", "/", "/tmp")
+
+If you want to run a command in a shell, consider using something like
+the following – but be aware that not all systems, particularly
+Windows, will have a /bin/sh program:
+
+ exec.Command("/bin/sh", "-c", "ls | grep Awesome")
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1005](https://staticcheck.dev/docs/checks/#SA1005)
+
+
+## `SA1007`: Invalid URL in net/url.Parse
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1007": true}`.
+
+Package documentation: [SA1007](https://staticcheck.dev/docs/checks/#SA1007)
+
+
+## `SA1008`: Non-canonical key in http.Header map
+
+
+Keys in http.Header maps are canonical, meaning they follow a specific
+combination of uppercase and lowercase letters. Methods such as
+http.Header.Add and http.Header.Del convert inputs into this canonical
+form before manipulating the map.
+
+When manipulating http.Header maps directly, as opposed to using the
+provided methods, care should be taken to stick to canonical form in
+order to avoid inconsistencies. The following piece of code
+demonstrates one such inconsistency:
+
+ h := http.Header{}
+ h["etag"] = []string{"1234"}
+ h.Add("etag", "5678")
+ fmt.Println(h)
+
+ // Output:
+ // map[Etag:[5678] etag:[1234]]
+
+The easiest way of obtaining the canonical form of a key is to use
+http.CanonicalHeaderKey.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1008](https://staticcheck.dev/docs/checks/#SA1008)
+
+
+## `SA1010`: (*regexp.Regexp).FindAll called with n == 0, which will always return zero results
+
+
+If n >= 0, the function returns at most n matches/submatches. To
+return all results, specify a negative number.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1010": true}`.
+
+Package documentation: [SA1010](https://staticcheck.dev/docs/checks/#SA1010)
+
+
+## `SA1011`: Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1011": true}`.
+
+Package documentation: [SA1011](https://staticcheck.dev/docs/checks/#SA1011)
+
+
+## `SA1012`: A nil context.Context is being passed to a function, consider using context.TODO instead
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1012](https://staticcheck.dev/docs/checks/#SA1012)
+
+
+## `SA1013`: io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1013](https://staticcheck.dev/docs/checks/#SA1013)
+
+
+## `SA1014`: Non-pointer value passed to Unmarshal or Decode
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1014": true}`.
+
+Package documentation: [SA1014](https://staticcheck.dev/docs/checks/#SA1014)
+
+
+## `SA1015`: Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions
+
+
+Before Go 1.23, time.Tickers had to be closed to be able to be garbage
+collected. Since time.Tick doesn't make it possible to close the underlying
+ticker, using it repeatedly would leak memory.
+
+Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1015": true}`.
+
+Package documentation: [SA1015](https://staticcheck.dev/docs/checks/#SA1015)
+
+
+## `SA1016`: Trapping a signal that cannot be trapped
+
+
+Not all signals can be intercepted by a process. Specifically, on
+UNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are
+never passed to the process, but instead handled directly by the
+kernel. It is therefore pointless to try and handle these signals.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA1016](https://staticcheck.dev/docs/checks/#SA1016)
+
+
+## `SA1017`: Channels used with os/signal.Notify should be buffered
+
+
+The os/signal package uses non-blocking channel sends when delivering
+signals. If the receiving end of the channel isn't ready and the
+channel is either unbuffered or full, the signal will be dropped. To
+avoid missing signals, the channel should be buffered and of the
+appropriate size. For a channel used for notification of just one
+signal value, a buffer of size 1 is sufficient.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1017": true}`.
+
+Package documentation: [SA1017](https://staticcheck.dev/docs/checks/#SA1017)
+
+
+## `SA1018`: strings.Replace called with n == 0, which does nothing
+
+
+With n == 0, zero instances will be replaced. To replace all
+instances, use a negative number, or use strings.ReplaceAll.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1018": true}`.
+
+Package documentation: [SA1018](https://staticcheck.dev/docs/checks/#SA1018)
+
+
+## `SA1020`: Using an invalid host:port pair with a net.Listen-related function
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1020": true}`.
+
+Package documentation: [SA1020](https://staticcheck.dev/docs/checks/#SA1020)
+
+
+## `SA1021`: Using bytes.Equal to compare two net.IP
+
+
+A net.IP stores an IPv4 or IPv6 address as a slice of bytes. The
+length of the slice for an IPv4 address, however, can be either 4 or
+16 bytes long, using different ways of representing IPv4 addresses. In
+order to correctly compare two net.IPs, the net.IP.Equal method should
+be used, as it takes both representations into account.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1021": true}`.
+
+Package documentation: [SA1021](https://staticcheck.dev/docs/checks/#SA1021)
+
+
+## `SA1023`: Modifying the buffer in an io.Writer implementation
+
+
+Write must not modify the slice data, even temporarily.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1023": true}`.
+
+Package documentation: [SA1023](https://staticcheck.dev/docs/checks/#SA1023)
+
+
+## `SA1024`: A string cutset contains duplicate characters
+
+
+The strings.TrimLeft and strings.TrimRight functions take cutsets, not
+prefixes. A cutset is treated as a set of characters to remove from a
+string. For example,
+
+ strings.TrimLeft("42133word", "1234")
+
+will result in the string "word" – any characters that are 1, 2, 3 or
+4 are cut from the left of the string.
+
+In order to remove one string from another, use strings.TrimPrefix instead.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1024": true}`.
+
+Package documentation: [SA1024](https://staticcheck.dev/docs/checks/#SA1024)
+
+
+## `SA1025`: It is not possible to use (*time.Timer).Reset's return value correctly
+
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1025": true}`.
+
+Package documentation: [SA1025](https://staticcheck.dev/docs/checks/#SA1025)
+
+
+## `SA1026`: Cannot marshal channels or functions
+
+
+Available since
+ 2019.2
+
+
+Default: off. Enable by setting `"analyses": {"SA1026": true}`.
+
+Package documentation: [SA1026](https://staticcheck.dev/docs/checks/#SA1026)
+
+
+## `SA1027`: Atomic access to 64-bit variable must be 64-bit aligned
+
+
+On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to
+arrange for 64-bit alignment of 64-bit words accessed atomically. The
+first word in a variable or in an allocated struct, array, or slice
+can be relied upon to be 64-bit aligned.
+
+You can use the structlayout tool to inspect the alignment of fields
+in a struct.
+
+Available since
+ 2019.2
+
+
+Default: off. Enable by setting `"analyses": {"SA1027": true}`.
+
+Package documentation: [SA1027](https://staticcheck.dev/docs/checks/#SA1027)
+
+
+## `SA1028`: sort.Slice can only be used on slices
+
+
+The first argument of sort.Slice must be a slice.
+
+Available since
+ 2020.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1028": true}`.
+
+Package documentation: [SA1028](https://staticcheck.dev/docs/checks/#SA1028)
+
+
+## `SA1029`: Inappropriate key in call to context.WithValue
+
+
+The provided key must be comparable and should not be
+of type string or any other built-in type to avoid collisions between
+packages using context. Users of WithValue should define their own
+types for keys.
+
+To avoid allocating when assigning to an interface{},
+context keys often have concrete type struct{}. Alternatively,
+exported context key variables' static type should be a pointer or
+interface.
+
+Available since
+ 2020.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1029": true}`.
+
+Package documentation: [SA1029](https://staticcheck.dev/docs/checks/#SA1029)
+
+
+## `SA1030`: Invalid argument in call to a strconv function
+
+
+This check validates the format, number base and bit size arguments of
+the various parsing and formatting functions in strconv.
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1030": true}`.
+
+Package documentation: [SA1030](https://staticcheck.dev/docs/checks/#SA1030)
+
+
+## `SA1031`: Overlapping byte slices passed to an encoder
+
+
+In an encoding function of the form Encode(dst, src), dst and
+src were found to reference the same memory. This can result in
+src bytes being overwritten before they are read, when the encoder
+writes more than one byte per src byte.
+
+Available since
+ 2024.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1031": true}`.
+
+Package documentation: [SA1031](https://staticcheck.dev/docs/checks/#SA1031)
+
+
+## `SA1032`: Wrong order of arguments to errors.Is
+
+
+The first argument of the function errors.Is is the error
+that we have and the second argument is the error we're trying to match against.
+For example:
+
+ if errors.Is(err, io.EOF) { ... }
+
+This check detects some cases where the two arguments have been swapped. It
+flags any calls where the first argument is referring to a package-level error
+variable, such as
+
+ if errors.Is(io.EOF, err) { /* this is wrong */ }
+
+Available since
+ 2024.1
+
+
+Default: off. Enable by setting `"analyses": {"SA1032": true}`.
+
+Package documentation: [SA1032](https://staticcheck.dev/docs/checks/#SA1032)
+
+
+## `SA2001`: Empty critical section, did you mean to defer the unlock?
+
+
+Empty critical sections of the kind
+
+ mu.Lock()
+ mu.Unlock()
+
+are very often a typo, and the following was intended instead:
+
+ mu.Lock()
+ defer mu.Unlock()
+
+Do note that sometimes empty critical sections can be useful, as a
+form of signaling to wait on another goroutine. Many times, there are
+simpler ways of achieving the same effect. When that isn't the case,
+the code should be amply commented to avoid confusion. Combining such
+comments with a //lint:ignore directive can be used to suppress this
+rare false positive.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA2001](https://staticcheck.dev/docs/checks/#SA2001)
+
+
+## `SA2002`: Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA2002": true}`.
+
+Package documentation: [SA2002](https://staticcheck.dev/docs/checks/#SA2002)
+
+
+## `SA2003`: Deferred Lock right after locking, likely meant to defer Unlock instead
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA2003": true}`.
+
+Package documentation: [SA2003](https://staticcheck.dev/docs/checks/#SA2003)
+
+
+## `SA3000`: TestMain doesn't call os.Exit, hiding test failures
+
+
+Test executables (and in turn 'go test') exit with a non-zero status
+code if any tests failed. When specifying your own TestMain function,
+it is your responsibility to arrange for this, by calling os.Exit with
+the correct code. The correct code is returned by (*testing.M).Run, so
+the usual way of implementing TestMain is to end it with
+os.Exit(m.Run()).
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA3000](https://staticcheck.dev/docs/checks/#SA3000)
+
+
+## `SA3001`: Assigning to b.N in benchmarks distorts the results
+
+
+The testing package dynamically sets b.N to improve the reliability of
+benchmarks and uses it in computations to determine the duration of a
+single operation. Benchmark code must not alter b.N as this would
+falsify results.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA3001](https://staticcheck.dev/docs/checks/#SA3001)
+
+
+## `SA4000`: Binary operator has identical expressions on both sides
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4000](https://staticcheck.dev/docs/checks/#SA4000)
+
+
+## `SA4001`: &*x gets simplified to x, it does not copy x
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4001](https://staticcheck.dev/docs/checks/#SA4001)
+
+
+## `SA4003`: Comparing unsigned values against negative values is pointless
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4003](https://staticcheck.dev/docs/checks/#SA4003)
+
+
+## `SA4004`: The loop exits unconditionally after one iteration
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4004](https://staticcheck.dev/docs/checks/#SA4004)
+
+
+## `SA4005`: Field assignment that will never be observed. Did you mean to use a pointer receiver?
+
+
+Available since
+ 2021.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4005": true}`.
+
+Package documentation: [SA4005](https://staticcheck.dev/docs/checks/#SA4005)
+
+
+## `SA4006`: A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4006": true}`.
+
+Package documentation: [SA4006](https://staticcheck.dev/docs/checks/#SA4006)
+
+
+## `SA4008`: The variable in the loop condition never changes, are you incrementing the wrong variable?
+
+
+For example:
+
+ for i := 0; i < 10; j++ { ... }
+
+This may also occur when a loop can only execute once because of unconditional
+control flow that terminates the loop. For example, when a loop body contains an
+unconditional break, return, or panic:
+
+ func f() {
+ panic("oops")
+ }
+ func g() {
+ for i := 0; i < 10; i++ {
+ // f unconditionally calls panic, which means "i" is
+ // never incremented.
+ f()
+ }
+ }
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4008": true}`.
+
+Package documentation: [SA4008](https://staticcheck.dev/docs/checks/#SA4008)
+
+
+## `SA4009`: A function argument is overwritten before its first use
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4009": true}`.
+
+Package documentation: [SA4009](https://staticcheck.dev/docs/checks/#SA4009)
+
+
+## `SA4010`: The result of append will never be observed anywhere
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4010": true}`.
+
+Package documentation: [SA4010](https://staticcheck.dev/docs/checks/#SA4010)
+
+
+## `SA4011`: Break statement with no effect. Did you mean to break out of an outer loop?
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4011](https://staticcheck.dev/docs/checks/#SA4011)
+
+
+## `SA4012`: Comparing a value against NaN even though no value is equal to NaN
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4012": true}`.
+
+Package documentation: [SA4012](https://staticcheck.dev/docs/checks/#SA4012)
+
+
+## `SA4013`: Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4013](https://staticcheck.dev/docs/checks/#SA4013)
+
+
+## `SA4014`: An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4014](https://staticcheck.dev/docs/checks/#SA4014)
+
+
+## `SA4015`: Calling functions like math.Ceil on floats converted from integers doesn't do anything useful
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4015": true}`.
+
+Package documentation: [SA4015](https://staticcheck.dev/docs/checks/#SA4015)
+
+
+## `SA4016`: Certain bitwise operations, such as x ^ 0, do not do anything useful
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4016](https://staticcheck.dev/docs/checks/#SA4016)
+
+
+## `SA4017`: Discarding the return values of a function without side effects, making the call pointless
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4017": true}`.
+
+Package documentation: [SA4017](https://staticcheck.dev/docs/checks/#SA4017)
+
+
+## `SA4018`: Self-assignment of variables
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4018": true}`.
+
+Package documentation: [SA4018](https://staticcheck.dev/docs/checks/#SA4018)
+
+
+## `SA4019`: Multiple, identical build constraints in the same file
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA4019](https://staticcheck.dev/docs/checks/#SA4019)
+
+
+## `SA4020`: Unreachable case clause in a type switch
+
+
+In a type switch like the following
+
+ type T struct{}
+ func (T) Read(b []byte) (int, error) { return 0, nil }
+
+ var v interface{} = T{}
+
+ switch v.(type) {
+ case io.Reader:
+ // ...
+ case T:
+ // unreachable
+ }
+
+the second case clause can never be reached because T implements
+io.Reader and case clauses are evaluated in source order.
+
+Another example:
+
+ type T struct{}
+ func (T) Read(b []byte) (int, error) { return 0, nil }
+ func (T) Close() error { return nil }
+
+ var v interface{} = T{}
+
+ switch v.(type) {
+ case io.Reader:
+ // ...
+ case io.ReadCloser:
+ // unreachable
+ }
+
+Even though T has a Close method and thus implements io.ReadCloser,
+io.Reader will always match first. The method set of io.Reader is a
+subset of io.ReadCloser. Thus it is impossible to match the second
+case without matching the first case.
+
+
+Structurally equivalent interfaces
+
+A special case of the previous example are structurally identical
+interfaces. Given these declarations
+
+ type T error
+ type V error
+
+ func doSomething() error {
+ err, ok := doAnotherThing()
+ if ok {
+ return T(err)
+ }
+
+ return U(err)
+ }
+
+the following type switch will have an unreachable case clause:
+
+ switch doSomething().(type) {
+ case T:
+ // ...
+ case V:
+ // unreachable
+ }
+
+T will always match before V because they are structurally equivalent
+and therefore doSomething()'s return value implements both.
+
+Available since
+ 2019.2
+
+
+Default: on.
+
+Package documentation: [SA4020](https://staticcheck.dev/docs/checks/#SA4020)
+
+
+## `SA4022`: Comparing the address of a variable against nil
+
+
+Code such as 'if &x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.
+
+Available since
+ 2020.1
+
+
+Default: on.
+
+Package documentation: [SA4022](https://staticcheck.dev/docs/checks/#SA4022)
+
+
+## `SA4023`: Impossible comparison of interface value with untyped nil
+
+
+Under the covers, interfaces are implemented as two elements, a
+type T and a value V. V is a concrete value such as an int,
+struct or pointer, never an interface itself, and has type T. For
+instance, if we store the int value 3 in an interface, the
+resulting interface value has, schematically, (T=int, V=3). The
+value V is also known as the interface's dynamic value, since a
+given interface variable might hold different values V (and
+corresponding types T) during the execution of the program.
+
+An interface value is nil only if the V and T are both
+unset, (T=nil, V is not set), In particular, a nil interface will
+always hold a nil type. If we store a nil pointer of type *int
+inside an interface value, the inner type will be *int regardless
+of the value of the pointer: (T=*int, V=nil). Such an interface
+value will therefore be non-nil even when the pointer value V
+inside is nil.
+
+This situation can be confusing, and arises when a nil value is
+stored inside an interface value such as an error return:
+
+ func returnsError() error {
+ var p *MyError = nil
+ if bad() {
+ p = ErrBad
+ }
+ return p // Will always return a non-nil error.
+ }
+
+If all goes well, the function returns a nil p, so the return
+value is an error interface value holding (T=*MyError, V=nil).
+This means that if the caller compares the returned error to nil,
+it will always look as if there was an error even if nothing bad
+happened. To return a proper nil error to the caller, the
+function must return an explicit nil:
+
+ func returnsError() error {
+ if bad() {
+ return ErrBad
+ }
+ return nil
+ }
+
+It's a good idea for functions that return errors always to use
+the error type in their signature (as we did above) rather than a
+concrete type such as *MyError, to help guarantee the error is
+created correctly. As an example, os.Open returns an error even
+though, if not nil, it's always of concrete type *os.PathError.
+
+Similar situations to those described here can arise whenever
+interfaces are used. Just keep in mind that if any concrete value
+has been stored in the interface, the interface will not be nil.
+For more information, see The Laws of
+Reflection at https://golang.org/doc/articles/laws_of_reflection.html.
+
+This text has been copied from
+https://golang.org/doc/faq#nil_error, licensed under the Creative
+Commons Attribution 3.0 License.
+
+Available since
+ 2020.2
+
+
+Default: off. Enable by setting `"analyses": {"SA4023": true}`.
+
+Package documentation: [SA4023](https://staticcheck.dev/docs/checks/#SA4023)
+
+
+## `SA4024`: Checking for impossible return value from a builtin function
+
+
+Return values of the len and cap builtins cannot be negative.
+
+See https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.
+
+Example:
+
+ if len(slice) < 0 {
+ fmt.Println("unreachable code")
+ }
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [SA4024](https://staticcheck.dev/docs/checks/#SA4024)
+
+
+## `SA4025`: Integer division of literals that results in zero
+
+
+When dividing two integer constants, the result will
+also be an integer. Thus, a division such as 2 / 3 results in 0.
+This is true for all of the following examples:
+
+ _ = 2 / 3
+ const _ = 2 / 3
+ const _ float64 = 2 / 3
+ _ = float64(2 / 3)
+
+Staticcheck will flag such divisions if both sides of the division are
+integer literals, as it is highly unlikely that the division was
+intended to truncate to zero. Staticcheck will not flag integer
+division involving named constants, to avoid noisy positives.
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [SA4025](https://staticcheck.dev/docs/checks/#SA4025)
+
+
+## `SA4026`: Go constants cannot express negative zero
+
+
+In IEEE 754 floating point math, zero has a sign and can be positive
+or negative. This can be useful in certain numerical code.
+
+Go constants, however, cannot express negative zero. This means that
+the literals -0.0 and 0.0 have the same ideal value (zero) and
+will both represent positive zero at runtime.
+
+To explicitly and reliably create a negative zero, you can use the
+math.Copysign function: math.Copysign(0, -1).
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [SA4026](https://staticcheck.dev/docs/checks/#SA4026)
+
+
+## `SA4027`: (*net/url.URL).Query returns a copy, modifying it doesn't change the URL
+
+
+(*net/url.URL).Query parses the current value of net/url.URL.RawQuery
+and returns it as a map of type net/url.Values. Subsequent changes to
+this map will not affect the URL unless the map gets encoded and
+assigned to the URL's RawQuery.
+
+As a consequence, the following code pattern is an expensive no-op:
+u.Query().Add(key, value).
+
+Available since
+ 2021.1
+
+
+Default: on.
+
+Package documentation: [SA4027](https://staticcheck.dev/docs/checks/#SA4027)
+
+
+## `SA4028`: x % 1 is always zero
+
+
+Available since
+ 2022.1
+
+
+Default: on.
+
+Package documentation: [SA4028](https://staticcheck.dev/docs/checks/#SA4028)
+
+
+## `SA4029`: Ineffective attempt at sorting slice
+
+
+sort.Float64Slice, sort.IntSlice, and sort.StringSlice are
+types, not functions. Doing x = sort.StringSlice(x) does nothing,
+especially not sort any values. The correct usage is
+sort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),
+but there are more convenient helpers, namely sort.Float64s,
+sort.Ints, and sort.Strings.
+
+Available since
+ 2022.1
+
+
+Default: on.
+
+Package documentation: [SA4029](https://staticcheck.dev/docs/checks/#SA4029)
+
+
+## `SA4030`: Ineffective attempt at generating random number
+
+
+Functions in the math/rand package that accept upper limits, such
+as Intn, generate random numbers in the half-open interval [0,n). In
+other words, the generated numbers will be >= 0 and < n – they
+don't include n. rand.Intn(1) therefore doesn't generate 0
+or 1, it always generates 0.
+
+Available since
+ 2022.1
+
+
+Default: on.
+
+Package documentation: [SA4030](https://staticcheck.dev/docs/checks/#SA4030)
+
+
+## `SA4031`: Checking never-nil value against nil
+
+
+Available since
+ 2022.1
+
+
+Default: off. Enable by setting `"analyses": {"SA4031": true}`.
+
+Package documentation: [SA4031](https://staticcheck.dev/docs/checks/#SA4031)
+
+
+## `SA4032`: Comparing runtime.GOOS or runtime.GOARCH against impossible value
+
+
+Available since
+ 2024.1
+
+
+Default: on.
+
+Package documentation: [SA4032](https://staticcheck.dev/docs/checks/#SA4032)
+
+
+## `SA5000`: Assignment to nil map
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA5000": true}`.
+
+Package documentation: [SA5000](https://staticcheck.dev/docs/checks/#SA5000)
+
+
+## `SA5001`: Deferring Close before checking for a possible error
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA5001](https://staticcheck.dev/docs/checks/#SA5001)
+
+
+## `SA5002`: The empty for loop ('for {}') spins and can block the scheduler
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA5002": true}`.
+
+Package documentation: [SA5002](https://staticcheck.dev/docs/checks/#SA5002)
+
+
+## `SA5003`: Defers in infinite loops will never execute
+
+
+Defers are scoped to the surrounding function, not the surrounding
+block. In a function that never returns, i.e. one containing an
+infinite loop, defers will never execute.
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA5003](https://staticcheck.dev/docs/checks/#SA5003)
+
+
+## `SA5004`: 'for { select { ...' with an empty default branch spins
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA5004](https://staticcheck.dev/docs/checks/#SA5004)
+
+
+## `SA5005`: The finalizer references the finalized object, preventing garbage collection
+
+
+A finalizer is a function associated with an object that runs when the
+garbage collector is ready to collect said object, that is when the
+object is no longer referenced by anything.
+
+If the finalizer references the object, however, it will always remain
+as the final reference to that object, preventing the garbage
+collector from collecting the object. The finalizer will never run,
+and the object will never be collected, leading to a memory leak. That
+is why the finalizer should instead use its first argument to operate
+on the object. That way, the number of references can temporarily go
+to zero before the object is being passed to the finalizer.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA5005": true}`.
+
+Package documentation: [SA5005](https://staticcheck.dev/docs/checks/#SA5005)
+
+
+## `SA5007`: Infinite recursive call
+
+
+A function that calls itself recursively needs to have an exit
+condition. Otherwise it will recurse forever, until the system runs
+out of memory.
+
+This issue can be caused by simple bugs such as forgetting to add an
+exit condition. It can also happen "on purpose". Some languages have
+tail call optimization which makes certain infinite recursive calls
+safe to use. Go, however, does not implement TCO, and as such a loop
+should be used instead.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA5007": true}`.
+
+Package documentation: [SA5007](https://staticcheck.dev/docs/checks/#SA5007)
+
+
+## `SA5008`: Invalid struct tag
+
+
+Available since
+ 2019.2
+
+
+Default: on.
+
+Package documentation: [SA5008](https://staticcheck.dev/docs/checks/#SA5008)
+
+
+## `SA5010`: Impossible type assertion
+
+
+Some type assertions can be statically proven to be
+impossible. This is the case when the method sets of both
+arguments of the type assertion conflict with each other, for
+example by containing the same method with different
+signatures.
+
+The Go compiler already applies this check when asserting from an
+interface value to a concrete type. If the concrete type misses
+methods from the interface, or if function signatures don't match,
+then the type assertion can never succeed.
+
+This check applies the same logic when asserting from one interface to
+another. If both interface types contain the same method but with
+different signatures, then the type assertion can never succeed,
+either.
+
+Available since
+ 2020.1
+
+
+Default: off. Enable by setting `"analyses": {"SA5010": true}`.
+
+Package documentation: [SA5010](https://staticcheck.dev/docs/checks/#SA5010)
+
+
+## `SA5011`: Possible nil pointer dereference
+
+
+A pointer is being dereferenced unconditionally, while
+also being checked against nil in another place. This suggests that
+the pointer may be nil and dereferencing it may panic. This is
+commonly a result of improperly ordered code or missing return
+statements. Consider the following examples:
+
+ func fn(x *int) {
+ fmt.Println(*x)
+
+ // This nil check is equally important for the previous dereference
+ if x != nil {
+ foo(*x)
+ }
+ }
+
+ func TestFoo(t *testing.T) {
+ x := compute()
+ if x == nil {
+ t.Errorf("nil pointer received")
+ }
+
+ // t.Errorf does not abort the test, so if x is nil, the next line will panic.
+ foo(*x)
+ }
+
+Staticcheck tries to deduce which functions abort control flow.
+For example, it is aware that a function will not continue
+execution after a call to panic or log.Fatal. However, sometimes
+this detection fails, in particular in the presence of
+conditionals. Consider the following example:
+
+ func Log(msg string, level int) {
+ fmt.Println(msg)
+ if level == levelFatal {
+ os.Exit(1)
+ }
+ }
+
+ func Fatal(msg string) {
+ Log(msg, levelFatal)
+ }
+
+ func fn(x *int) {
+ if x == nil {
+ Fatal("unexpected nil pointer")
+ }
+ fmt.Println(*x)
+ }
+
+Staticcheck will flag the dereference of x, even though it is perfectly
+safe. Staticcheck is not able to deduce that a call to
+Fatal will exit the program. For the time being, the easiest
+workaround is to modify the definition of Fatal like so:
+
+ func Fatal(msg string) {
+ Log(msg, levelFatal)
+ panic("unreachable")
+ }
+
+We also hard-code functions from common logging packages such as
+logrus. Please file an issue if we're missing support for a
+popular package.
+
+Available since
+ 2020.1
+
+
+Default: off. Enable by setting `"analyses": {"SA5011": true}`.
+
+Package documentation: [SA5011](https://staticcheck.dev/docs/checks/#SA5011)
+
+
+## `SA5012`: Passing odd-sized slice to function expecting even size
+
+
+Some functions that take slices as parameters expect the slices to have an even number of elements.
+Often, these functions treat elements in a slice as pairs.
+For example, strings.NewReplacer takes pairs of old and new strings,
+and calling it with an odd number of elements would be an error.
+
+Available since
+ 2020.2
+
+
+Default: off. Enable by setting `"analyses": {"SA5012": true}`.
+
+Package documentation: [SA5012](https://staticcheck.dev/docs/checks/#SA5012)
+
+
+## `SA6000`: Using regexp.Match or related in a loop, should use regexp.Compile
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA6000": true}`.
+
+Package documentation: [SA6000](https://staticcheck.dev/docs/checks/#SA6000)
+
+
+## `SA6001`: Missing an optimization opportunity when indexing maps by byte slices
+
+
+Map keys must be comparable, which precludes the use of byte slices.
+This usually leads to using string keys and converting byte slices to
+strings.
+
+Normally, a conversion of a byte slice to a string needs to copy the data and
+causes allocations. The compiler, however, recognizes m[string(b)] and
+uses the data of b directly, without copying it, because it knows that
+the data can't change during the map lookup. This leads to the
+counter-intuitive situation that
+
+ k := string(b)
+ println(m[k])
+ println(m[k])
+
+will be less efficient than
+
+ println(m[string(b)])
+ println(m[string(b)])
+
+because the first version needs to copy and allocate, while the second
+one does not.
+
+For some history on this optimization, check out commit
+f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA6001": true}`.
+
+Package documentation: [SA6001](https://staticcheck.dev/docs/checks/#SA6001)
+
+
+## `SA6002`: Storing non-pointer values in sync.Pool allocates memory
+
+
+A sync.Pool is used to avoid unnecessary allocations and reduce the
+amount of work the garbage collector has to do.
+
+When passing a value that is not a pointer to a function that accepts
+an interface, the value needs to be placed on the heap, which means an
+additional allocation. Slices are a common thing to put in sync.Pools,
+and they're structs with 3 fields (length, capacity, and a pointer to
+an array). In order to avoid the extra allocation, one should store a
+pointer to the slice instead.
+
+See the comments on https://go-review.googlesource.com/c/go/+/24371
+that discuss this problem.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA6002": true}`.
+
+Package documentation: [SA6002](https://staticcheck.dev/docs/checks/#SA6002)
+
+
+## `SA6003`: Converting a string to a slice of runes before ranging over it
+
+
+You may want to loop over the runes in a string. Instead of converting
+the string to a slice of runes and looping over that, you can loop
+over the string itself. That is,
+
+ for _, r := range s {}
+
+and
+
+ for _, r := range []rune(s) {}
+
+will yield the same values. The first version, however, will be faster
+and avoid unnecessary memory allocations.
+
+Do note that if you are interested in the indices, ranging over a
+string and over a slice of runes will yield different indices. The
+first one yields byte offsets, while the second one yields indices in
+the slice of runes.
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA6003": true}`.
+
+Package documentation: [SA6003](https://staticcheck.dev/docs/checks/#SA6003)
+
+
+## `SA6005`: Inefficient string comparison with strings.ToLower or strings.ToUpper
+
+
+Converting two strings to the same case and comparing them like so
+
+ if strings.ToLower(s1) == strings.ToLower(s2) {
+ ...
+ }
+
+is significantly more expensive than comparing them with
+strings.EqualFold(s1, s2). This is due to memory usage as well as
+computational complexity.
+
+strings.ToLower will have to allocate memory for the new strings, as
+well as convert both strings fully, even if they differ on the very
+first byte. strings.EqualFold, on the other hand, compares the strings
+one character at a time. It doesn't need to create two intermediate
+strings and can return as soon as the first non-matching character has
+been found.
+
+For a more in-depth explanation of this issue, see
+https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/
+
+Available since
+ 2019.2
+
+
+Default: on.
+
+Package documentation: [SA6005](https://staticcheck.dev/docs/checks/#SA6005)
+
+
+## `SA6006`: Using io.WriteString to write []byte
+
+
+Using io.WriteString to write a slice of bytes, as in
+
+ io.WriteString(w, string(b))
+
+is both unnecessary and inefficient. Converting from []byte to string
+has to allocate and copy the data, and we could simply use w.Write(b)
+instead.
+
+Available since
+ 2024.1
+
+
+Default: on.
+
+Package documentation: [SA6006](https://staticcheck.dev/docs/checks/#SA6006)
+
+
+## `SA9001`: Defers in range loops may not run when you expect them to
+
+
+Available since
+ 2017.1
+
+
+Default: off. Enable by setting `"analyses": {"SA9001": true}`.
+
+Package documentation: [SA9001](https://staticcheck.dev/docs/checks/#SA9001)
+
+
+## `SA9002`: Using a non-octal os.FileMode that looks like it was meant to be in octal.
+
+
+Available since
+ 2017.1
+
+
+Default: on.
+
+Package documentation: [SA9002](https://staticcheck.dev/docs/checks/#SA9002)
+
+
+## `SA9003`: Empty body in an if or else branch
+
+
+Available since
+ 2017.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"SA9003": true}`.
+
+Package documentation: [SA9003](https://staticcheck.dev/docs/checks/#SA9003)
+
+
+## `SA9004`: Only the first constant has an explicit type
+
+
+In a constant declaration such as the following:
+
+ const (
+ First byte = 1
+ Second = 2
+ )
+
+the constant Second does not have the same type as the constant First.
+This construct shouldn't be confused with
+
+ const (
+ First byte = iota
+ Second
+ )
+
+where First and Second do indeed have the same type. The type is only
+passed on when no explicit value is assigned to the constant.
+
+When declaring enumerations with explicit values it is therefore
+important not to write
+
+ const (
+ EnumFirst EnumType = 1
+ EnumSecond = 2
+ EnumThird = 3
+ )
+
+This discrepancy in types can cause various confusing behaviors and
+bugs.
+
+
+Wrong type in variable declarations
+
+The most obvious issue with such incorrect enumerations expresses
+itself as a compile error:
+
+ package pkg
+
+ const (
+ EnumFirst uint8 = 1
+ EnumSecond = 2
+ )
+
+ func fn(useFirst bool) {
+ x := EnumSecond
+ if useFirst {
+ x = EnumFirst
+ }
+ }
+
+fails to compile with
+
+ ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
+
+
+Losing method sets
+
+A more subtle issue occurs with types that have methods and optional
+interfaces. Consider the following:
+
+ package main
+
+ import "fmt"
+
+ type Enum int
+
+ func (e Enum) String() string {
+ return "an enum"
+ }
+
+ const (
+ EnumFirst Enum = 1
+ EnumSecond = 2
+ )
+
+ func main() {
+ fmt.Println(EnumFirst)
+ fmt.Println(EnumSecond)
+ }
+
+This code will output
+
+ an enum
+ 2
+
+as EnumSecond has no explicit type, and thus defaults to int.
+
+Available since
+ 2019.1
+
+
+Default: on.
+
+Package documentation: [SA9004](https://staticcheck.dev/docs/checks/#SA9004)
+
+
+## `SA9005`: Trying to marshal a struct with no public fields nor custom marshaling
+
+
+The encoding/json and encoding/xml packages only operate on exported
+fields in structs, not unexported ones. It is usually an error to try
+to (un)marshal structs that only consist of unexported fields.
+
+This check will not flag calls involving types that define custom
+marshaling behavior, e.g. via MarshalJSON methods. It will also not
+flag empty structs.
+
+Available since
+ 2019.2
+
+
+Default: off. Enable by setting `"analyses": {"SA9005": true}`.
+
+Package documentation: [SA9005](https://staticcheck.dev/docs/checks/#SA9005)
+
+
+## `SA9006`: Dubious bit shifting of a fixed size integer value
+
+
+Bit shifting a value past its size will always clear the value.
+
+For instance:
+
+ v := int8(42)
+ v >>= 8
+
+will always result in 0.
+
+This check flags bit shifting operations on fixed size integer values only.
+That is, int, uint and uintptr are never flagged to avoid potential false
+positives in somewhat exotic but valid bit twiddling tricks:
+
+ // Clear any value above 32 bits if integers are more than 32 bits.
+ func f(i int) int {
+ v := i >> 32
+ v = v << 32
+ return i-v
+ }
+
+Available since
+ 2020.2
+
+
+Default: on.
+
+Package documentation: [SA9006](https://staticcheck.dev/docs/checks/#SA9006)
+
+
+## `SA9007`: Deleting a directory that shouldn't be deleted
+
+
+It is virtually never correct to delete system directories such as
+/tmp or the user's home directory. However, it can be fairly easy to
+do by mistake, for example by mistakenly using os.TempDir instead
+of ioutil.TempDir, or by forgetting to add a suffix to the result
+of os.UserHomeDir.
+
+Writing
+
+ d := os.TempDir()
+ defer os.RemoveAll(d)
+
+in your unit tests will have a devastating effect on the stability of your system.
+
+This check flags attempts at deleting the following directories:
+
+- os.TempDir
+- os.UserCacheDir
+- os.UserConfigDir
+- os.UserHomeDir
+
+Available since
+ 2022.1
+
+
+Default: off. Enable by setting `"analyses": {"SA9007": true}`.
+
+Package documentation: [SA9007](https://staticcheck.dev/docs/checks/#SA9007)
+
+
+## `SA9008`: else branch of a type assertion is probably not reading the right value
+
+
+When declaring variables as part of an if statement (like in 'if
+foo := ...; foo {'), the same variables will also be in the scope of
+the else branch. This means that in the following example
+
+ if x, ok := x.(int); ok {
+ // ...
+ } else {
+ fmt.Printf("unexpected type %T", x)
+ }
+
+x in the else branch will refer to the x from x, ok
+:=; it will not refer to the x that is being type-asserted. The
+result of a failed type assertion is the zero value of the type that
+is being asserted to, so x in the else branch will always have the
+value 0 and the type int.
+
+Available since
+ 2022.1
+
+
+Default: off. Enable by setting `"analyses": {"SA9008": true}`.
+
+Package documentation: [SA9008](https://staticcheck.dev/docs/checks/#SA9008)
+
+
+## `SA9009`: Ineffectual Go compiler directive
+
+
+A potential Go compiler directive was found, but is ineffectual as it begins
+with whitespace.
+
+Available since
+ 2024.1
+
+
+Default: on.
+
+Package documentation: [SA9009](https://staticcheck.dev/docs/checks/#SA9009)
+
+
+## `ST1000`: Incorrect or missing package comment
+
+
+Packages must have a package comment that is formatted according to
+the guidelines laid out in
+https://go.dev/wiki/CodeReviewComments#package-comments.
+
+Available since
+ 2019.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"ST1000": true}`.
+
+Package documentation: [ST1000](https://staticcheck.dev/docs/checks/#ST1000)
+
+
+## `ST1001`: Dot imports are discouraged
+
+
+Dot imports that aren't in external test packages are discouraged.
+
+The dot_import_whitelist option can be used to whitelist certain
+imports.
+
+Quoting Go Code Review Comments:
+
+> The import . form can be useful in tests that, due to circular
+> dependencies, cannot be made part of the package being tested:
+>
+> package foo_test
+>
+> import (
+> "bar/testutil" // also imports "foo"
+> . "foo"
+> )
+>
+> In this case, the test file cannot be in package foo because it
+> uses bar/testutil, which imports foo. So we use the import .
+> form to let the file pretend to be part of package foo even though
+> it is not. Except for this one case, do not use import . in your
+> programs. It makes the programs much harder to read because it is
+> unclear whether a name like Quux is a top-level identifier in the
+> current package or in an imported package.
+
+Available since
+ 2019.1
+
+Options
+ dot_import_whitelist
+
+
+Default: off. Enable by setting `"analyses": {"ST1001": true}`.
+
+Package documentation: [ST1001](https://staticcheck.dev/docs/checks/#ST1001)
+
+
+## `ST1003`: Poorly chosen identifier
+
+
+Identifiers, such as variable and package names, follow certain rules.
+
+See the following links for details:
+
+- https://go.dev/doc/effective_go#package-names
+- https://go.dev/doc/effective_go#mixed-caps
+- https://go.dev/wiki/CodeReviewComments#initialisms
+- https://go.dev/wiki/CodeReviewComments#variable-names
+
+Available since
+ 2019.1, non-default
+
+Options
+ initialisms
+
+
+Default: off. Enable by setting `"analyses": {"ST1003": true}`.
+
+Package documentation: [ST1003](https://staticcheck.dev/docs/checks/#ST1003)
+
+
+## `ST1005`: Incorrectly formatted error string
+
+
+Error strings follow a set of guidelines to ensure uniformity and good
+composability.
+
+Quoting Go Code Review Comments:
+
+> Error strings should not be capitalized (unless beginning with
+> proper nouns or acronyms) or end with punctuation, since they are
+> usually printed following other context. That is, use
+> fmt.Errorf("something bad") not fmt.Errorf("Something bad"), so
+> that log.Printf("Reading %s: %v", filename, err) formats without a
+> spurious capital letter mid-message.
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1005": true}`.
+
+Package documentation: [ST1005](https://staticcheck.dev/docs/checks/#ST1005)
+
+
+## `ST1006`: Poorly chosen receiver name
+
+
+Quoting Go Code Review Comments:
+
+> The name of a method's receiver should be a reflection of its
+> identity; often a one or two letter abbreviation of its type
+> suffices (such as "c" or "cl" for "Client"). Don't use generic
+> names such as "me", "this" or "self", identifiers typical of
+> object-oriented languages that place more emphasis on methods as
+> opposed to functions. The name need not be as descriptive as that
+> of a method argument, as its role is obvious and serves no
+> documentary purpose. It can be very short as it will appear on
+> almost every line of every method of the type; familiarity admits
+> brevity. Be consistent, too: if you call the receiver "c" in one
+> method, don't call it "cl" in another.
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1006": true}`.
+
+Package documentation: [ST1006](https://staticcheck.dev/docs/checks/#ST1006)
+
+
+## `ST1008`: A function's error value should be its last return value
+
+
+A function's error value should be its last return value.
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1008": true}`.
+
+Package documentation: [ST1008](https://staticcheck.dev/docs/checks/#ST1008)
+
+
+## `ST1011`: Poorly chosen name for variable of type time.Duration
+
+
+time.Duration values represent an amount of time, which is represented
+as a count of nanoseconds. An expression like 5 * time.Microsecond
+yields the value 5000. It is therefore not appropriate to suffix a
+variable of type time.Duration with any time unit, such as Msec or
+Milli.
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1011": true}`.
+
+Package documentation: [ST1011](https://staticcheck.dev/docs/checks/#ST1011)
+
+
+## `ST1012`: Poorly chosen name for error variable
+
+
+Error variables that are part of an API should be called errFoo or
+ErrFoo.
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1012": true}`.
+
+Package documentation: [ST1012](https://staticcheck.dev/docs/checks/#ST1012)
+
+
+## `ST1013`: Should use constants for HTTP error codes, not magic numbers
+
+
+HTTP has a tremendous number of status codes. While some of those are
+well known (200, 400, 404, 500), most of them are not. The net/http
+package provides constants for all status codes that are part of the
+various specifications. It is recommended to use these constants
+instead of hard-coding magic numbers, to vastly improve the
+readability of your code.
+
+Available since
+ 2019.1
+
+Options
+ http_status_code_whitelist
+
+
+Default: off. Enable by setting `"analyses": {"ST1013": true}`.
+
+Package documentation: [ST1013](https://staticcheck.dev/docs/checks/#ST1013)
+
+
+## `ST1015`: A switch's default case should be the first or last case
+
+
+Available since
+ 2019.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1015": true}`.
+
+Package documentation: [ST1015](https://staticcheck.dev/docs/checks/#ST1015)
+
+
+## `ST1016`: Use consistent method receiver names
+
+
+Available since
+ 2019.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"ST1016": true}`.
+
+Package documentation: [ST1016](https://staticcheck.dev/docs/checks/#ST1016)
+
+
+## `ST1017`: Don't use Yoda conditions
+
+
+Yoda conditions are conditions of the kind 'if 42 == x', where the
+literal is on the left side of the comparison. These are a common
+idiom in languages in which assignment is an expression, to avoid bugs
+of the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of
+bug, we prefer the more idiomatic 'if x == 42'.
+
+Available since
+ 2019.2
+
+
+Default: off. Enable by setting `"analyses": {"ST1017": true}`.
+
+Package documentation: [ST1017](https://staticcheck.dev/docs/checks/#ST1017)
+
+
+## `ST1018`: Avoid zero-width and control characters in string literals
+
+
+Available since
+ 2019.2
+
+
+Default: off. Enable by setting `"analyses": {"ST1018": true}`.
+
+Package documentation: [ST1018](https://staticcheck.dev/docs/checks/#ST1018)
+
+
+## `ST1019`: Importing the same package multiple times
+
+
+Go allows importing the same package multiple times, as long as
+different import aliases are being used. That is, the following
+bit of code is valid:
+
+ import (
+ "fmt"
+ fumpt "fmt"
+ format "fmt"
+ _ "fmt"
+ )
+
+However, this is very rarely done on purpose. Usually, it is a
+sign of code that got refactored, accidentally adding duplicate
+import statements. It is also a rarely known feature, which may
+contribute to confusion.
+
+Do note that sometimes, this feature may be used
+intentionally (see for example
+https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)
+– if you want to allow this pattern in your code base, you're
+advised to disable this check.
+
+Available since
+ 2020.1
+
+
+Default: off. Enable by setting `"analyses": {"ST1019": true}`.
+
+Package documentation: [ST1019](https://staticcheck.dev/docs/checks/#ST1019)
+
+
+## `ST1020`: The documentation of an exported function should start with the function's name
+
+
+Doc comments work best as complete sentences, which
+allow a wide variety of automated presentations. The first sentence
+should be a one-sentence summary that starts with the name being
+declared.
+
+If every doc comment begins with the name of the item it describes,
+you can use the doc subcommand of the go tool and run the output
+through grep.
+
+See https://go.dev/doc/effective_go#commentary for more
+information on how to write good documentation.
+
+Available since
+ 2020.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"ST1020": true}`.
+
+Package documentation: [ST1020](https://staticcheck.dev/docs/checks/#ST1020)
+
+
+## `ST1021`: The documentation of an exported type should start with type's name
+
+
+Doc comments work best as complete sentences, which
+allow a wide variety of automated presentations. The first sentence
+should be a one-sentence summary that starts with the name being
+declared.
+
+If every doc comment begins with the name of the item it describes,
+you can use the doc subcommand of the go tool and run the output
+through grep.
+
+See https://go.dev/doc/effective_go#commentary for more
+information on how to write good documentation.
+
+Available since
+ 2020.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"ST1021": true}`.
+
+Package documentation: [ST1021](https://staticcheck.dev/docs/checks/#ST1021)
+
+
+## `ST1022`: The documentation of an exported variable or constant should start with variable's name
+
+
+Doc comments work best as complete sentences, which
+allow a wide variety of automated presentations. The first sentence
+should be a one-sentence summary that starts with the name being
+declared.
+
+If every doc comment begins with the name of the item it describes,
+you can use the doc subcommand of the go tool and run the output
+through grep.
+
+See https://go.dev/doc/effective_go#commentary for more
+information on how to write good documentation.
+
+Available since
+ 2020.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"ST1022": true}`.
+
+Package documentation: [ST1022](https://staticcheck.dev/docs/checks/#ST1022)
+
+
+## `ST1023`: Redundant type in variable declaration
+
+
+Available since
+ 2021.1, non-default
+
+
+Default: off. Enable by setting `"analyses": {"ST1023": true}`.
+
+Package documentation: [ST1023](https://staticcheck.dev/docs/checks/#)
+
## `appends`: check for missing values after append
@@ -323,7 +3515,7 @@ A similar diagnostic and fix are produced for a format string of "%s:%s".
Default: on.
-Package documentation: [hostport](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/hostport)
+Package documentation: [hostport](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/hostport)
## `httpresponse`: check for mistakes using HTTP responses
@@ -492,6 +3684,9 @@ following command:
$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
+(Do not use "go get -tool" to add gopls as a dependency of your
+module; gopls commands must be built from their release branch.)
+
If the tool warns of conflicting fixes, you may need to run it more
than once until it has applied all fixes cleanly. This command is
not an officially supported interface and may change in the future.
diff --git a/gopls/doc/assets/subtypes.png b/gopls/doc/assets/subtypes.png
new file mode 100644
index 00000000000..9868a56a77d
Binary files /dev/null and b/gopls/doc/assets/subtypes.png differ
diff --git a/gopls/doc/assets/supertypes.png b/gopls/doc/assets/supertypes.png
new file mode 100644
index 00000000000..59e1c79750d
Binary files /dev/null and b/gopls/doc/assets/supertypes.png differ
diff --git a/gopls/doc/features/diagnostics.md b/gopls/doc/features/diagnostics.md
index 6be7a43493a..75c29d5f795 100644
--- a/gopls/doc/features/diagnostics.md
+++ b/gopls/doc/features/diagnostics.md
@@ -314,12 +314,8 @@ dorky details and deletia:
currently: atomicalign deepequalerrors nilness sortslice unusedwrite embeddirective
-- **staticcheck**: four suites:
-
- add(simple.Analyzers, nil)
- add(staticcheck.Analyzers - SA5009, SA5011
- add(stylecheck.Analyzers, nil)
- add(quickfix.Analyzers, nil)
+- **staticcheck**: four suites (S=simple, SA=static analysis, QF=quickfix, ST=stylecheck)
+ Only a hand-picked subset of them are enabled by default.
- **Experimental analyzers**. Gopls has some analyzers that are not
enabled by default, because they produce too high a rate of false
diff --git a/gopls/doc/features/navigation.md b/gopls/doc/features/navigation.md
index f3454f7188c..11b40797cd4 100644
--- a/gopls/doc/features/navigation.md
+++ b/gopls/doc/features/navigation.md
@@ -94,12 +94,34 @@ Interfaces and concrete types are matched using method sets:
location of the declaration of each type that implements
the interface.
- When invoked on a **concrete type**,
- it returns the locations of the matching interface types.
+ it returns the locations of the matching interface types.
- When invoked on an **interface method**, it returns the corresponding
methods of the types that satisfy the interface.
- When invoked on a **concrete method**,
it returns the locations of the matching interface methods.
+For example:
+- `implementation(io.Reader)` includes subinterfaces such as `io.ReadCloser`,
+ and concrete implementations such as `*os.File`. It also includes
+ other declarations equivalent to `io.Reader`.
+- `implementation(os.File)` includes only interfaces, such as
+ `io.Reader` and `io.ReadCloser`.
+
+The LSP's Implementation feature has a built-in bias towards subtypes,
+possibly because in languages such as Java and C++ the relationship
+between a type and its supertypes is explicit in the syntax, so the
+corresponding "Go to interfaces" operation can be achieved as sequence
+of two or more "Go to definition" steps: the first to visit the type
+declaration, and the rest to sequentially visit ancestors.
+(See https://github.com/microsoft/language-server-protocol/issues/2037.)
+
+In Go, where there is no syntactic relationship between two types, a
+search is required when navigating in either direction between
+subtypes and supertypes. The heuristic above works well in many cases,
+but it is not possible to ask for the superinterfaces of
+`io.ReadCloser`. For more explicit navigation between subtypes and
+supertypes, use the [Type Hierarchy](#Type Hierarchy) feature.
+
Only non-trivial interfaces are considered; no implementations are
reported for type `any`.
@@ -274,11 +296,40 @@ of `fmt.Stringer` through the guts of `fmt.Sprint:`
-Caveats:
-- In some cases dynamic function calls are (erroneously) included in
- the output; see golang/go#68153.
-
Client support:
- **VS Code**: `Show Call Hierarchy` menu item (`⌥⇧H`) opens [Call hierarchy view](https://code.visualstudio.com/docs/cpp/cpp-ide#_call-hierarchy) (note: docs refer to C++ but the idea is the same for Go).
- **Emacs + eglot**: Not standard; install with `(package-vc-install "https://github.com/dolmens/eglot-hierarchy")`. Use `M-x eglot-hierarchy-call-hierarchy` to show the direct incoming calls to the selected function; use a prefix argument (`C-u`) to show the direct outgoing calls. There is no way to expand the tree.
- **CLI**: `gopls call_hierarchy file.go:#offset` shows outgoing and incoming calls.
+
+
+## Type Hierarchy
+
+The LSP TypeHierarchy mechanism consists of three queries that
+together enable clients to present a hierarchical view of a portion of
+the subtyping relation over named types.
+
+- [`textDocument/prepareTypeHierarchy`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareTypeHierarchy) returns an [item](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyItem) describing the named type at the current position;
+- [`typeHierarchyItem/subtypes`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchy_subtypes) returns the set of subtypes of the selected (interface) type; and
+- [`typeHierarchy/supertypes`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchy_supertypes) returns the set of supertypes (interface types) of the selected type.
+
+Invoke the command while selecting the name of a type.
+
+As with an Implementation query, a type hierarchy query reports
+function-local types only within the same package as the query type.
+Also the result does not include alias types, only defined types.
+
+
+
+
+
+Caveats:
+
+- The type hierarchy supports only named types and their assignability
+ relation. By contrast, the Implementations request also reports the
+ relation between unnamed `func` types and function declarations,
+ function literals, and dynamic calls of values of those types.
+
+Client support:
+- **VS Code**: `Show Type Hierarchy` menu item opens [Type hierarchy view](https://code.visualstudio.com/docs/java/java-editing#_type-hierarchy) (note: docs refer to Java but the idea is the same for Go).
+- **Emacs + eglot**: Support added in March 2025. Use `M-x eglot-show-call-hierarchy`.
+- **CLI**: not yet supported.
diff --git a/gopls/doc/features/transformation.md b/gopls/doc/features/transformation.md
index a72ff676832..91b6c46b74d 100644
--- a/gopls/doc/features/transformation.md
+++ b/gopls/doc/features/transformation.md
@@ -79,15 +79,17 @@ Gopls supports the following code actions:
- [`refactor.extract.variable`](#extract)
- [`refactor.extract.variable-all`](#extract)
- [`refactor.inline.call`](#refactor.inline.call)
+- [`refactor.rewrite.addTags`](#refactor.rewrite.addTags)
- [`refactor.rewrite.changeQuote`](#refactor.rewrite.changeQuote)
- [`refactor.rewrite.fillStruct`](#refactor.rewrite.fillStruct)
- [`refactor.rewrite.fillSwitch`](#refactor.rewrite.fillSwitch)
- [`refactor.rewrite.invertIf`](#refactor.rewrite.invertIf)
- [`refactor.rewrite.joinLines`](#refactor.rewrite.joinLines)
-- [`refactor.rewrite.removeUnusedParam`](#refactor.rewrite.removeUnusedParam)
-- [`refactor.rewrite.splitLines`](#refactor.rewrite.splitLines)
- [`refactor.rewrite.moveParamLeft`](#refactor.rewrite.moveParamLeft)
- [`refactor.rewrite.moveParamRight`](#refactor.rewrite.moveParamRight)
+- [`refactor.rewrite.removeTags`](#refactor.rewrite.removeTags)
+- [`refactor.rewrite.removeUnusedParam`](#refactor.rewrite.removeUnusedParam)
+- [`refactor.rewrite.splitLines`](#refactor.rewrite.splitLines)
Gopls reports some code actions twice, with two different kinds, so
that they appear in multiple UI elements: simplifications,
@@ -315,11 +317,30 @@ Similar problems may arise with packages that use reflection, such as
`encoding/json` or `text/template`. There is no substitute for good
judgment and testing.
+Special cases:
+
+- When renaming the declaration of a method receiver, the tool also
+ attempts to rename the receivers of all other methods associated
+ with the same named type. Each other receiver that cannot be fully
+ renamed is quietly skipped. Renaming any _use_ of a receiver affects
+ only that variable.
+
+ ```go
+ type Counter struct { x int }
+
+ Rename here to affect only this method
+ ↓
+ func (c *Counter) Inc() { c.x++ }
+ func (c *Counter) Dec() { c.x++ }
+ ↑
+ Rename here to affect all methods
+ ```
+
+- Renaming a package declaration additionally causes the package's
+ directory to be renamed.
+
Some tips for best results:
-- There is currently no special support for renaming all receivers of
- a family of methods at once, so you will need to rename one receiver
- one at a time (golang/go#41892).
- The safety checks performed by the Rename algorithm require type
information. If the program is grossly malformed, there may be
insufficient information for it to run (golang/go#41870),
@@ -823,3 +844,18 @@ When the cursor is on a dot import gopls can offer the "Eliminate dot import"
code action, which removes the dot from the import and qualifies uses of the
package throughout the file. This code action is offered only if
each use of the package can be qualified without collisions with existing names.
+
+
+### `refactor.rewrite.addTags`: Add struct tags
+
+When the cursor is within a struct, this code action adds to each field a `json`
+struct tag that specifies its JSON name, using lower case with underscores
+(e.g. LinkTarget becomes link_target). For a highlighted selection, it only
+adds tags on selected fields.
+
+
+### `refactor.rewrite.removeTags`: Remove struct tags
+
+When the cursor is within a struct, this code action clears struct tags on
+all struct fields. For a highlighted selection, it removes tags from only
+the selected fields.
diff --git a/gopls/doc/release/v0.19.0.md b/gopls/doc/release/v0.19.0.md
index f6208417ebc..b8f53a72304 100644
--- a/gopls/doc/release/v0.19.0.md
+++ b/gopls/doc/release/v0.19.0.md
@@ -7,6 +7,47 @@
# New features
+## "Rename" of method receivers
+
+The Rename operation, when applied to the declaration of a method
+receiver, now also attempts to rename the receivers of all other
+methods associated with the same named type. Each other receiver that
+cannot be fully renamed is quietly skipped.
+
+Renaming a _use_ of a method receiver continues to affect only that
+variable.
+
+```go
+type Counter struct { x int }
+
+ Rename here to affect only this method
+ ↓
+func (c *Counter) Inc() { c.x++ }
+func (c *Counter) Dec() { c.x++ }
+ ↑
+ Rename here to affect all methods
+```
+
+## Many `staticcheck` analyzers are enabled by default
+
+Slightly more than half of the analyzers in the
+[Staticcheck](https://staticcheck.dev/docs/checks) suite are now
+enabled by default. This subset has been chosen for precision and
+efficiency.
+
+Prevously, Staticcheck analyzers (all of them) would be run only if
+the experimental `staticcheck` boolean option was set to `true`. This
+value continues to enable the complete set, and a value of `false`
+continues to disable the complete set. Leaving the option unspecified
+enables the preferred subset of analyzers.
+
+Staticcheck analyzers, like all other analyzers, can be explicitly
+enabled or disabled using the `analyzers` configuration setting; this
+setting now takes precedence over the `staticcheck` setting, so,
+regardless of what value of `staticcheck` you use (true/false/unset),
+you can make adjustments to your preferred set of analyzers.
+
+
## "Implementations" supports signature types
The Implementations query reports the correspondence between abstract
@@ -33,6 +74,40 @@ and queries using signatures should be invoked on a `func` or `(` token.
Only the local (same-package) algorithm is currently supported.
TODO: implement global.
+## Go to Implementation
+
+The "Go to Implementation" operation now reports relationships between
+interfaces. Gopls now uses the concreteness of the query type to
+determine whether a query is "downwards" (from an interface to the
+types that implement it) or "upwards" (from a concrete type to the
+interfaces to which it may be assigned). So, for example:
+
+- `implementation(io.Reader)` subinterfaces such as `io.ReadCloser`,
+ and concrete implementations such as `*os.File`.
+
+- `implementation(os.File)` includes only interfaces, such as
+ `io.Reader` and `io.ReadCloser`.
+
+To request an "upwards" query starting from an interface, for example
+to find the superinterfaces of `io.ReadCloser`, use the Type Hierarchy
+feature described below.
+(See https://github.com/microsoft/language-server-protocol/issues/2037.)
+
+## Support for Type Hierarchy
+
+
+
+Gopls now implements the three LSP methods related to the Type
+Hierarchy viewer: `textDocument/prepareTypeHierarchy`,
+`typeHierarchy/supertypes`, `typeHierarchy/subtypes`.
+
+In VS Code, select "Show Type Hierarchy" from the context menu
+to see a tree widget displaying all the supertypes or subtypes
+of the selected named type.
+
+
+
+
## "Eliminate dot import" code action
@@ -45,4 +120,19 @@ with its name.
Gopls now automatically adds the appropriate `package` clause to newly created Go files,
so that you can immediately get started writing the interesting part.
-It requires client support for `workspace/didCreateFiles`
\ No newline at end of file
+It requires client support for `workspace/didCreateFiles`
+
+## Add/remove tags from struct fields
+
+Gopls now provides two new code actions, available on an entire struct
+or some of its fields, that allow you to add and remove struct tags.
+It adds only 'json' tags with a snakecase naming format, or clears all
+tags within the selection.
+
+Add tags example:
+```go
+type Info struct {
+ LinkTarget string -> LinkTarget string `json:"link_target"`
+ ...
+}
+```
\ No newline at end of file
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 1f4f5746524..00415bb36f4 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -349,10 +349,28 @@ Default: `{}`.
**This setting is experimental and may be deleted.**
-staticcheck enables additional analyses from staticcheck.io.
+staticcheck configures the default set of analyses staticcheck.io.
These analyses are documented on
[Staticcheck's website](https://staticcheck.io/docs/checks/).
+The "staticcheck" option has three values:
+- false: disable all staticcheck analyzers
+- true: enable all staticcheck analyzers
+- unset: enable a subset of staticcheck analyzers
+ selected by gopls maintainers for runtime efficiency
+ and analytic precision.
+
+Regardless of this setting, individual analyzers can be
+selectively enabled or disabled using the `analyses` setting.
+
+Default: `false`.
+
+
+### `staticcheckProvided bool`
+
+**This setting is experimental and may be deleted.**
+
+
Default: `false`.
diff --git a/gopls/doc/vim.md b/gopls/doc/vim.md
index 444a7d6ff31..eedac5925f4 100644
--- a/gopls/doc/vim.md
+++ b/gopls/doc/vim.md
@@ -56,7 +56,7 @@ Use [prabirshrestha/vim-lsp], with the following configuration:
augroup LspGo
au!
autocmd User lsp_setup call lsp#register_server({
- \ 'name': 'go-lang',
+ \ 'name': 'gopls',
\ 'cmd': {server_info->['gopls']},
\ 'whitelist': ['go'],
\ })
diff --git a/gopls/go.mod b/gopls/go.mod
index c09e2daf7bd..96c3fbb127a 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -3,13 +3,14 @@ module golang.org/x/tools/gopls
go 1.24.2
require (
+ github.com/fatih/gomodifytags v1.17.1-0.20250423142747-f3939df9aa3c
github.com/google/go-cmp v0.6.0
github.com/jba/templatecheck v0.7.1
golang.org/x/mod v0.24.0
- golang.org/x/sync v0.13.0
- golang.org/x/sys v0.32.0
- golang.org/x/telemetry v0.0.0-20250220152412-165e2f84edbc
- golang.org/x/text v0.24.0
+ golang.org/x/sync v0.14.0
+ golang.org/x/sys v0.33.0
+ golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3
+ golang.org/x/text v0.25.0
golang.org/x/tools v0.30.0
golang.org/x/vuln v1.1.4
gopkg.in/yaml.v3 v3.0.1
@@ -20,6 +21,8 @@ require (
require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
+ github.com/fatih/camelcase v1.0.0 // indirect
+ github.com/fatih/structtag v1.2.0 // indirect
github.com/google/safehtml v0.1.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20250218142911-aa4b98e5adaa // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
diff --git a/gopls/go.sum b/gopls/go.sum
index f5a9bbde4ca..27f999d51a4 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -1,5 +1,11 @@
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
+github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
+github.com/fatih/gomodifytags v1.17.1-0.20250423142747-f3939df9aa3c h1:dDSgAjoOMp8da3egfz0t2S+t8RGOpEmEXZubcGuc0Bg=
+github.com/fatih/gomodifytags v1.17.1-0.20250423142747-f3939df9aa3c/go.mod h1:YVLagR57bBxMai8IAEc7V4E/MWUYi0oUutLrZcTcnI8=
+github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
+github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -16,7 +22,7 @@ github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGK
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp/typeparams v0.0.0-20250218142911-aa4b98e5adaa h1:Br3+0EZZohShrmVVc85znGpxw7Ca8hsUJlrdT/JQGw8=
golang.org/x/exp/typeparams v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -25,27 +31,27 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
-golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
+golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
-golang.org/x/telemetry v0.0.0-20250220152412-165e2f84edbc h1:HS+G1Mhh2dxM8ObutfYKdjfD7zpkyeP/UxeRnJpIZtQ=
-golang.org/x/telemetry v0.0.0-20250220152412-165e2f84edbc/go.mod h1:bDzXkYUaHzz51CtDy5kh/jR4lgPxsdbqC37kp/dzhCc=
+golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
+golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
-golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
+golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/gopls/internal/analysis/fillreturns/fillreturns.go b/gopls/internal/analysis/fillreturns/fillreturns.go
index a90105f6f56..b2cc1caf872 100644
--- a/gopls/internal/analysis/fillreturns/fillreturns.go
+++ b/gopls/internal/analysis/fillreturns/fillreturns.go
@@ -12,6 +12,7 @@ import (
"go/format"
"go/types"
"regexp"
+ "slices"
"strings"
"golang.org/x/tools/go/analysis"
@@ -49,7 +50,7 @@ outer:
if !ok {
continue // no position information
}
- curErr, ok := cursor.Root(inspect).FindPos(start, end)
+ curErr, ok := cursor.Root(inspect).FindByPos(start, end)
if !ok {
continue // can't find node
}
@@ -134,7 +135,7 @@ outer:
if match != nil {
fixed[i] = match
- remaining = append(remaining[:idx], remaining[idx+1:]...)
+ remaining = slices.Delete(remaining, idx, idx+1)
} else {
names, ok := matches[retTyp]
if !ok {
diff --git a/gopls/internal/analysis/fillstruct/fillstruct.go b/gopls/internal/analysis/fillstruct/fillstruct.go
index 62f7d77f58e..5a18da9a221 100644
--- a/gopls/internal/analysis/fillstruct/fillstruct.go
+++ b/gopls/internal/analysis/fillstruct/fillstruct.go
@@ -76,7 +76,7 @@ func Diagnose(f *ast.File, start, end token.Pos, pkg *types.Package, info *types
// Are any fields in need of filling?
var fillableFields []string
- for i := 0; i < fieldCount; i++ {
+ for i := range fieldCount {
field := tStruct.Field(i)
// Ignore fields that are not accessible in the current package.
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
@@ -182,7 +182,7 @@ func SuggestedFix(cpkg *cache.Package, pgf *parsego.File, start, end token.Pos)
}
var fieldTyps []types.Type
- for i := 0; i < fieldCount; i++ {
+ for i := range fieldCount {
field := tStruct.Field(i)
// Ignore fields that are not accessible in the current package.
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
diff --git a/gopls/internal/analysis/modernize/bloop.go b/gopls/internal/analysis/modernize/bloop.go
index 5bfb0b7d8e8..ea2359c7fb6 100644
--- a/gopls/internal/analysis/modernize/bloop.go
+++ b/gopls/internal/analysis/modernize/bloop.go
@@ -48,27 +48,25 @@ func bloop(pass *analysis.Pass) {
// Within the same function, delete all calls to
// b.{Start,Stop,Timer} that precede the loop.
filter := []ast.Node{(*ast.ExprStmt)(nil), (*ast.FuncLit)(nil)}
- curFn.Inspect(filter, func(cur cursor.Cursor, push bool) (descend bool) {
- if push {
- node := cur.Node()
- if is[*ast.FuncLit](node) {
- return false // don't descend into FuncLits (e.g. sub-benchmarks)
- }
- stmt := node.(*ast.ExprStmt)
- if stmt.Pos() > start {
- return false // not preceding: stop
- }
- if call, ok := stmt.X.(*ast.CallExpr); ok {
- obj := typeutil.Callee(info, call)
- if analysisinternal.IsMethodNamed(obj, "testing", "B", "StopTimer", "StartTimer", "ResetTimer") {
- // Delete call statement.
- // TODO(adonovan): delete following newline, or
- // up to start of next stmt? (May delete a comment.)
- edits = append(edits, analysis.TextEdit{
- Pos: stmt.Pos(),
- End: stmt.End(),
- })
- }
+ curFn.Inspect(filter, func(cur cursor.Cursor) (descend bool) {
+ node := cur.Node()
+ if is[*ast.FuncLit](node) {
+ return false // don't descend into FuncLits (e.g. sub-benchmarks)
+ }
+ stmt := node.(*ast.ExprStmt)
+ if stmt.Pos() > start {
+ return false // not preceding: stop
+ }
+ if call, ok := stmt.X.(*ast.CallExpr); ok {
+ obj := typeutil.Callee(info, call)
+ if analysisinternal.IsMethodNamed(obj, "testing", "B", "StopTimer", "StartTimer", "ResetTimer") {
+ // Delete call statement.
+ // TODO(adonovan): delete following newline, or
+ // up to start of next stmt? (May delete a comment.)
+ edits = append(edits, analysis.TextEdit{
+ Pos: stmt.Pos(),
+ End: stmt.End(),
+ })
}
}
return true
diff --git a/gopls/internal/analysis/modernize/doc.go b/gopls/internal/analysis/modernize/doc.go
index aa052540832..2c4b893f6d2 100644
--- a/gopls/internal/analysis/modernize/doc.go
+++ b/gopls/internal/analysis/modernize/doc.go
@@ -25,6 +25,9 @@
//
// $ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
//
+// (Do not use "go get -tool" to add gopls as a dependency of your
+// module; gopls commands must be built from their release branch.)
+//
// If the tool warns of conflicting fixes, you may need to run it more
// than once until it has applied all fixes cleanly. This command is
// not an officially supported interface and may change in the future.
diff --git a/gopls/internal/analysis/modernize/modernize.go b/gopls/internal/analysis/modernize/modernize.go
index b7e943a0c51..44992c3aa14 100644
--- a/gopls/internal/analysis/modernize/modernize.go
+++ b/gopls/internal/analysis/modernize/modernize.go
@@ -170,22 +170,10 @@ func enclosingFile(c cursor.Cursor) *ast.File {
// specified standard packages or their dependencies.
func within(pass *analysis.Pass, pkgs ...string) bool {
path := pass.Pkg.Path()
- return standard(path) &&
+ return analysisinternal.IsStdPackage(path) &&
moreiters.Contains(stdlib.Dependencies(pkgs...), path)
}
-// standard reports whether the specified package path belongs to a
-// package in the standard library (including internal dependencies).
-func standard(path string) bool {
- // A standard package has no dot in its first segment.
- // (It may yet have a dot, e.g. "vendor/golang.org/x/foo".)
- slash := strings.IndexByte(path, '/')
- if slash < 0 {
- slash = len(path)
- }
- return !strings.Contains(path[:slash], ".") && path != "testdata"
-}
-
var (
builtinAny = types.Universe.Lookup("any")
builtinAppend = types.Universe.Lookup("append")
diff --git a/gopls/internal/analysis/modernize/slicescontains.go b/gopls/internal/analysis/modernize/slicescontains.go
index e99474df6ab..78a569eeca9 100644
--- a/gopls/internal/analysis/modernize/slicescontains.go
+++ b/gopls/internal/analysis/modernize/slicescontains.go
@@ -129,6 +129,11 @@ func slicescontains(pass *analysis.Pass) {
isSliceElem(cond.Args[0]) &&
typeutil.Callee(info, cond) != nil { // not a conversion
+ // skip variadic functions
+ if sig, ok := info.TypeOf(cond.Fun).(*types.Signature); ok && sig.Variadic() {
+ return
+ }
+
funcName = "ContainsFunc"
arg2 = cond.Fun // "if predicate(elem)"
}
diff --git a/gopls/internal/analysis/modernize/stringscutprefix.go b/gopls/internal/analysis/modernize/stringscutprefix.go
index cd053539910..f8e9be63e3c 100644
--- a/gopls/internal/analysis/modernize/stringscutprefix.go
+++ b/gopls/internal/analysis/modernize/stringscutprefix.go
@@ -114,7 +114,7 @@ func stringscutprefix(pass *analysis.Pass) {
{
Pos: call.Fun.Pos(),
End: call.Fun.Pos(),
- NewText: []byte(fmt.Sprintf("%s, %s :=", after, okVarName)),
+ NewText: fmt.Appendf(nil, "%s, %s :=", after, okVarName),
},
{
Pos: call.Fun.Pos(),
@@ -124,7 +124,7 @@ func stringscutprefix(pass *analysis.Pass) {
{
Pos: call.End(),
End: call.End(),
- NewText: []byte(fmt.Sprintf("; %s ", okVarName)),
+ NewText: fmt.Appendf(nil, "; %s ", okVarName),
},
{
Pos: call1.Pos(),
diff --git a/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go b/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go
index 6116ce14838..03bcfc69904 100644
--- a/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go
+++ b/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go
@@ -146,3 +146,26 @@ func nopeNeedleHaystackDifferentTypes2(x error, args []any) {
}
}
}
+
+func nopeVariadicNamedContainsFunc(slice []int) bool {
+ for _, elem := range slice {
+ if variadicPredicate(elem) {
+ return true
+ }
+ }
+ return false
+}
+
+func variadicPredicate(int, ...any) bool
+
+func nopeVariadicContainsFunc(slice []int) bool {
+ f := func(int, ...any) bool {
+ return true
+ }
+ for _, elem := range slice {
+ if f(elem) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go.golden b/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go.golden
index 2d67395f203..67e5b544960 100644
--- a/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go.golden
+++ b/gopls/internal/analysis/modernize/testdata/src/slicescontains/slicescontains.go.golden
@@ -102,3 +102,26 @@ func nopeNeedleHaystackDifferentTypes2(x error, args []any) {
}
}
}
+
+func nopeVariadicNamedContainsFunc(slice []int) bool {
+ for _, elem := range slice {
+ if variadicPredicate(elem) {
+ return true
+ }
+ }
+ return false
+}
+
+func variadicPredicate(int, ...any) bool
+
+func nopeVariadicContainsFunc(slice []int) bool {
+ f := func(int, ...any) bool {
+ return true
+ }
+ for _, elem := range slice {
+ if f(elem) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/gopls/internal/analysis/nonewvars/nonewvars.go b/gopls/internal/analysis/nonewvars/nonewvars.go
index eeae7211c97..62383dc2309 100644
--- a/gopls/internal/analysis/nonewvars/nonewvars.go
+++ b/gopls/internal/analysis/nonewvars/nonewvars.go
@@ -43,7 +43,7 @@ func run(pass *analysis.Pass) (any, error) {
if !ok {
continue // can't get position info
}
- curErr, ok := cursor.Root(inspect).FindPos(start, end)
+ curErr, ok := cursor.Root(inspect).FindByPos(start, end)
if !ok {
continue // can't find errant node
}
diff --git a/gopls/internal/analysis/noresultvalues/noresultvalues.go b/gopls/internal/analysis/noresultvalues/noresultvalues.go
index 848f6532ce0..4f095c941c4 100644
--- a/gopls/internal/analysis/noresultvalues/noresultvalues.go
+++ b/gopls/internal/analysis/noresultvalues/noresultvalues.go
@@ -43,7 +43,7 @@ func run(pass *analysis.Pass) (any, error) {
if !ok {
continue // can't get position info
}
- curErr, ok := cursor.Root(inspect).FindPos(start, end)
+ curErr, ok := cursor.Root(inspect).FindByPos(start, end)
if !ok {
continue // can't find errant node
}
diff --git a/gopls/internal/analysis/unusedparams/unusedparams.go b/gopls/internal/analysis/unusedparams/unusedparams.go
index 12076c5f273..824711242da 100644
--- a/gopls/internal/analysis/unusedparams/unusedparams.go
+++ b/gopls/internal/analysis/unusedparams/unusedparams.go
@@ -124,194 +124,171 @@ func run(pass *analysis.Pass) (any, error) {
}
}
- // Inspect each file to see if it is generated.
- //
- // We do not want to report unused parameters in generated code itself,
- // however we need to include generated code in the overall analysis as
- // it may be calling functions in non-generated code.
- files := []ast.Node{(*ast.File)(nil)}
- cursor.Root(inspect).Inspect(files, func(c cursor.Cursor, push bool) bool {
- if !push {
- return true
- }
-
- isGenerated := ast.IsGenerated(c.Node().(*ast.File))
-
- // Descend into the file, check each non-address-taken function's parameters
- // are all used.
- funcs := []ast.Node{
- (*ast.FuncDecl)(nil),
- (*ast.FuncLit)(nil),
- }
- c.Inspect(funcs, func(c cursor.Cursor, push bool) bool {
- // (We always return true so that we visit nested FuncLits.)
- if !push {
- return true
+ // Check each non-address-taken function's parameters are all used.
+funcloop:
+ for c := range cursor.Root(inspect).Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
+ var (
+ fn types.Object // function symbol (*Func, possibly *Var for a FuncLit)
+ ftype *ast.FuncType
+ body *ast.BlockStmt
+ )
+ switch n := c.Node().(type) {
+ case *ast.FuncDecl:
+ // We can't analyze non-Go functions.
+ if n.Body == nil {
+ continue
}
- var (
- fn types.Object // function symbol (*Func, possibly *Var for a FuncLit)
- ftype *ast.FuncType
- body *ast.BlockStmt
- )
- switch n := c.Node().(type) {
- case *ast.FuncDecl:
- // We can't analyze non-Go functions.
- if n.Body == nil {
- return true
- }
-
- // Ignore exported functions and methods: we
- // must assume they may be address-taken in
- // another package.
- if n.Name.IsExported() {
- return true
- }
-
- // Ignore methods that match the name of any
- // interface method declared in this package,
- // as the method's signature may need to conform
- // to the interface.
- if n.Recv != nil && unexportedIMethodNames[n.Name.Name] {
- return true
- }
-
- fn = pass.TypesInfo.Defs[n.Name].(*types.Func)
- ftype, body = n.Type, n.Body
+ // Ignore exported functions and methods: we
+ // must assume they may be address-taken in
+ // another package.
+ if n.Name.IsExported() {
+ continue
+ }
- case *ast.FuncLit:
- // Find the symbol for the variable (if any)
- // to which the FuncLit is bound.
- // (We don't bother to allow ParenExprs.)
- switch parent := c.Parent().Node().(type) {
- case *ast.AssignStmt:
- // f = func() {...}
- // f := func() {...}
- if ek, idx := c.ParentEdge(); ek == edge.AssignStmt_Rhs {
- // Inv: n == AssignStmt.Rhs[idx]
- if id, ok := parent.Lhs[idx].(*ast.Ident); ok {
- fn = pass.TypesInfo.ObjectOf(id)
+ // Ignore methods that match the name of any
+ // interface method declared in this package,
+ // as the method's signature may need to conform
+ // to the interface.
+ if n.Recv != nil && unexportedIMethodNames[n.Name.Name] {
+ continue
+ }
- // Edge case: f = func() {...}
- // should not count as a use.
- if pass.TypesInfo.Uses[id] != nil {
- usesOutsideCall[fn] = moreslices.Remove(usesOutsideCall[fn], id)
- }
+ fn = pass.TypesInfo.Defs[n.Name].(*types.Func)
+ ftype, body = n.Type, n.Body
+
+ case *ast.FuncLit:
+ // Find the symbol for the variable (if any)
+ // to which the FuncLit is bound.
+ // (We don't bother to allow ParenExprs.)
+ switch parent := c.Parent().Node().(type) {
+ case *ast.AssignStmt:
+ // f = func() {...}
+ // f := func() {...}
+ if ek, idx := c.ParentEdge(); ek == edge.AssignStmt_Rhs {
+ // Inv: n == AssignStmt.Rhs[idx]
+ if id, ok := parent.Lhs[idx].(*ast.Ident); ok {
+ fn = pass.TypesInfo.ObjectOf(id)
+
+ // Edge case: f = func() {...}
+ // should not count as a use.
+ if pass.TypesInfo.Uses[id] != nil {
+ usesOutsideCall[fn] = moreslices.Remove(usesOutsideCall[fn], id)
+ }
- if fn == nil && id.Name == "_" {
- // Edge case: _ = func() {...}
- // has no local var. Fake one.
- v := types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n))
- typesinternal.SetVarKind(v, typesinternal.LocalVar)
- fn = v
- }
+ if fn == nil && id.Name == "_" {
+ // Edge case: _ = func() {...}
+ // has no local var. Fake one.
+ v := types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n))
+ typesinternal.SetVarKind(v, typesinternal.LocalVar)
+ fn = v
}
}
+ }
- case *ast.ValueSpec:
- // var f = func() { ... }
- // (unless f is an exported package-level var)
- for i, val := range parent.Values {
- if val == n {
- v := pass.TypesInfo.Defs[parent.Names[i]]
- if !(v.Parent() == pass.Pkg.Scope() && v.Exported()) {
- fn = v
- }
- break
+ case *ast.ValueSpec:
+ // var f = func() { ... }
+ // (unless f is an exported package-level var)
+ for i, val := range parent.Values {
+ if val == n {
+ v := pass.TypesInfo.Defs[parent.Names[i]]
+ if !(v.Parent() == pass.Pkg.Scope() && v.Exported()) {
+ fn = v
}
+ break
}
}
-
- ftype, body = n.Type, n.Body
}
- // Ignore address-taken functions and methods: unused
- // parameters may be needed to conform to a func type.
- if fn == nil || len(usesOutsideCall[fn]) > 0 {
- return true
- }
+ ftype, body = n.Type, n.Body
+ }
- // If there are no parameters, there are no unused parameters.
- if ftype.Params.NumFields() == 0 {
- return true
- }
+ // Ignore address-taken functions and methods: unused
+ // parameters may be needed to conform to a func type.
+ if fn == nil || len(usesOutsideCall[fn]) > 0 {
+ continue
+ }
- // To reduce false positives, ignore functions with an
- // empty or panic body.
- //
- // We choose not to ignore functions whose body is a
- // single return statement (as earlier versions did)
- // func f() { return }
- // func f() { return g(...) }
- // as we suspect that was just heuristic to reduce
- // false positives in the earlier unsound algorithm.
- switch len(body.List) {
- case 0:
- // Empty body. Although the parameter is
- // unnecessary, it's pretty obvious to the
- // reader that that's the case, so we allow it.
- return true // func f() {}
- case 1:
- if stmt, ok := body.List[0].(*ast.ExprStmt); ok {
- // We allow a panic body, as it is often a
- // placeholder for a future implementation:
- // func f() { panic(...) }
- if call, ok := stmt.X.(*ast.CallExpr); ok {
- if fun, ok := call.Fun.(*ast.Ident); ok && fun.Name == "panic" {
- return true
- }
+ // If there are no parameters, there are no unused parameters.
+ if ftype.Params.NumFields() == 0 {
+ continue
+ }
+
+ // To reduce false positives, ignore functions with an
+ // empty or panic body.
+ //
+ // We choose not to ignore functions whose body is a
+ // single return statement (as earlier versions did)
+ // func f() { return }
+ // func f() { return g(...) }
+ // as we suspect that was just heuristic to reduce
+ // false positives in the earlier unsound algorithm.
+ switch len(body.List) {
+ case 0:
+ // Empty body. Although the parameter is
+ // unnecessary, it's pretty obvious to the
+ // reader that that's the case, so we allow it.
+ continue // func f() {}
+ case 1:
+ if stmt, ok := body.List[0].(*ast.ExprStmt); ok {
+ // We allow a panic body, as it is often a
+ // placeholder for a future implementation:
+ // func f() { panic(...) }
+ if call, ok := stmt.X.(*ast.CallExpr); ok {
+ if fun, ok := call.Fun.(*ast.Ident); ok && fun.Name == "panic" {
+ continue
}
}
}
+ }
- // Don't report diagnostics on generated files.
- if isGenerated {
- return true
+ // Don't report diagnostics on generated files.
+ // (We can't skip analysis of generated files, though.)
+ for curFile := range c.Enclosing((*ast.File)(nil)) {
+ if ast.IsGenerated(curFile.Node().(*ast.File)) {
+ continue funcloop
}
+ }
- // Report each unused parameter.
- for _, field := range ftype.Params.List {
- for _, id := range field.Names {
- if id.Name == "_" {
- continue
+ // Report each unused parameter.
+ for _, field := range ftype.Params.List {
+ for _, id := range field.Names {
+ if id.Name == "_" {
+ continue
+ }
+ param := pass.TypesInfo.Defs[id].(*types.Var)
+ if !usedVars[param] {
+ start, end := field.Pos(), field.End()
+ if len(field.Names) > 1 {
+ start, end = id.Pos(), id.End()
}
- param := pass.TypesInfo.Defs[id].(*types.Var)
- if !usedVars[param] {
- start, end := field.Pos(), field.End()
- if len(field.Names) > 1 {
- start, end = id.Pos(), id.End()
- }
- // This diagnostic carries both an edit-based fix to
- // rename the unused parameter, and a command-based fix
- // to remove it (see golang.RemoveUnusedParameter).
- pass.Report(analysis.Diagnostic{
- Pos: start,
- End: end,
- Message: fmt.Sprintf("unused parameter: %s", id.Name),
- Category: FixCategory,
- SuggestedFixes: []analysis.SuggestedFix{
- {
- Message: `Rename parameter to "_"`,
- TextEdits: []analysis.TextEdit{{
- Pos: id.Pos(),
- End: id.End(),
- NewText: []byte("_"),
- }},
- },
- {
- Message: fmt.Sprintf("Remove unused parameter %q", id.Name),
- // No TextEdits => computed by gopls command
- },
+ // This diagnostic carries both an edit-based fix to
+ // rename the unused parameter, and a command-based fix
+ // to remove it (see golang.RemoveUnusedParameter).
+ pass.Report(analysis.Diagnostic{
+ Pos: start,
+ End: end,
+ Message: fmt.Sprintf("unused parameter: %s", id.Name),
+ Category: FixCategory,
+ SuggestedFixes: []analysis.SuggestedFix{
+ {
+ Message: `Rename parameter to "_"`,
+ TextEdits: []analysis.TextEdit{{
+ Pos: id.Pos(),
+ End: id.End(),
+ NewText: []byte("_"),
+ }},
},
- })
- }
+ {
+ Message: fmt.Sprintf("Remove unused parameter %q", id.Name),
+ // No TextEdits => computed by gopls command
+ },
+ },
+ })
}
}
-
- return true
- })
- return true
- })
+ }
+ }
return nil, nil
}
diff --git a/gopls/internal/cache/analysis.go b/gopls/internal/cache/analysis.go
index cf5518cf79f..f63bcab2374 100644
--- a/gopls/internal/cache/analysis.go
+++ b/gopls/internal/cache/analysis.go
@@ -18,7 +18,6 @@ import (
"go/token"
"go/types"
"log"
- "maps"
urlpkg "net/url"
"path/filepath"
"reflect"
@@ -45,6 +44,7 @@ import (
"golang.org/x/tools/gopls/internal/util/frob"
"golang.org/x/tools/gopls/internal/util/moremaps"
"golang.org/x/tools/gopls/internal/util/persistent"
+ "golang.org/x/tools/gopls/internal/util/safetoken"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/facts"
@@ -127,11 +127,12 @@ func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]*metadata.Pac
// Filter and sort enabled root analyzers.
// A disabled analyzer may still be run if required by another.
- analyzers := analyzers(s.Options().Staticcheck)
- toSrc := make(map[*analysis.Analyzer]*settings.Analyzer)
- var enabledAnalyzers []*analysis.Analyzer // enabled subset + transitive requirements
- for _, a := range analyzers {
- if enabled, ok := s.Options().Analyses[a.Analyzer().Name]; enabled || !ok && a.EnabledByDefault() {
+ var (
+ toSrc = make(map[*analysis.Analyzer]*settings.Analyzer)
+ enabledAnalyzers []*analysis.Analyzer // enabled subset + transitive requirements
+ )
+ for _, a := range settings.AllAnalyzers {
+ if a.Enabled(s.Options()) {
toSrc[a.Analyzer()] = a
enabledAnalyzers = append(enabledAnalyzers, a.Analyzer())
}
@@ -139,7 +140,6 @@ func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]*metadata.Pac
sort.Slice(enabledAnalyzers, func(i, j int) bool {
return enabledAnalyzers[i].Name < enabledAnalyzers[j].Name
})
- analyzers = nil // prevent accidental use
enabledAnalyzers = requiredAnalyzers(enabledAnalyzers)
@@ -431,14 +431,6 @@ func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]*metadata.Pac
return results, nil
}
-func analyzers(staticcheck bool) []*settings.Analyzer {
- analyzers := slices.Collect(maps.Values(settings.DefaultAnalyzers))
- if staticcheck {
- analyzers = slices.AppendSeq(analyzers, maps.Values(settings.StaticcheckAnalyzers))
- }
- return analyzers
-}
-
func (an *analysisNode) decrefPreds() {
if an.unfinishedPreds.Add(-1) == 0 {
an.summary.Actions = nil
@@ -899,7 +891,6 @@ func (act *action) String() string {
func execActions(ctx context.Context, actions []*action) {
var wg sync.WaitGroup
for _, act := range actions {
- act := act
wg.Add(1)
go func() {
defer wg.Done()
@@ -1028,93 +1019,6 @@ func (act *action) exec(ctx context.Context) (any, *actionSummary, error) {
factFilter[reflect.TypeOf(f)] = true
}
- // posToLocation converts from token.Pos to protocol form.
- posToLocation := func(start, end token.Pos) (protocol.Location, error) {
- tokFile := apkg.pkg.FileSet().File(start)
-
- // Find existing mapper by file name.
- // (Don't require an exact token.File match
- // as the analyzer may have re-parsed the file.)
- var (
- mapper *protocol.Mapper
- fixed bool
- )
- for _, p := range apkg.pkg.CompiledGoFiles() {
- if p.Tok.Name() == tokFile.Name() {
- mapper = p.Mapper
- fixed = p.Fixed() // suppress some assertions after parser recovery
- break
- }
- }
- if mapper == nil {
- // The start position was not among the package's parsed
- // Go files, indicating that the analyzer added new files
- // to the FileSet.
- //
- // For example, the cgocall analyzer re-parses and
- // type-checks some of the files in a special environment;
- // and asmdecl and other low-level runtime analyzers call
- // ReadFile to parse non-Go files.
- // (This is a supported feature, documented at go/analysis.)
- //
- // In principle these files could be:
- //
- // - OtherFiles (non-Go files such as asm).
- // However, we set Pass.OtherFiles=[] because
- // gopls won't service "diagnose" requests
- // for non-Go files, so there's no point
- // reporting diagnostics in them.
- //
- // - IgnoredFiles (files tagged for other configs).
- // However, we set Pass.IgnoredFiles=[] because,
- // in most cases, zero-config gopls should create
- // another view that covers these files.
- //
- // - Referents of //line directives, as in cgo packages.
- // The file names in this case are not known a priori.
- // gopls generally tries to avoid honoring line directives,
- // but analyzers such as cgocall may honor them.
- //
- // In short, it's unclear how this can be reached
- // other than due to an analyzer bug.
- return protocol.Location{}, bug.Errorf("diagnostic location is not among files of package: %s", tokFile.Name())
- }
- // Inv: mapper != nil
-
- if end == token.NoPos {
- end = start
- }
-
- // debugging #64547
- fileStart := token.Pos(tokFile.Base())
- fileEnd := fileStart + token.Pos(tokFile.Size())
- if start < fileStart {
- if !fixed {
- bug.Reportf("start < start of file")
- }
- start = fileStart
- }
- if end < start {
- // This can happen if End is zero (#66683)
- // or a small positive displacement from zero
- // due to recursive Node.End() computation.
- // This usually arises from poor parser recovery
- // of an incomplete term at EOF.
- if !fixed {
- bug.Reportf("end < start of file")
- }
- end = fileEnd
- }
- if end > fileEnd+1 {
- if !fixed {
- bug.Reportf("end > end of file + 1")
- }
- end = fileEnd
- }
-
- return mapper.PosLocation(tokFile, start, end)
- }
-
// Now run the (pkg, analyzer) action.
var diagnostics []gobDiagnostic
@@ -1131,11 +1035,15 @@ func (act *action) exec(ctx context.Context) (any, *actionSummary, error) {
ResultOf: inputs,
Report: func(d analysis.Diagnostic) {
// Assert that SuggestedFixes are well formed.
+ //
+ // ValidateFixes allows a fix.End to be slightly beyond
+ // EOF to avoid spurious assertions when reporting
+ // fixes as the end of truncated files; see #71659.
if err := analysisinternal.ValidateFixes(apkg.pkg.FileSet(), analyzer, d.SuggestedFixes); err != nil {
bug.Reportf("invalid SuggestedFixes: %v", err)
d.SuggestedFixes = nil
}
- diagnostic, err := toGobDiagnostic(posToLocation, analyzer, d)
+ diagnostic, err := toGobDiagnostic(apkg.pkg, analyzer, d)
if err != nil {
// Don't bug.Report here: these errors all originate in
// posToLocation, and we can more accurately discriminate
@@ -1327,12 +1235,12 @@ type gobTextEdit struct {
// toGobDiagnostic converts an analysis.Diagnosic to a serializable gobDiagnostic,
// which requires expanding token.Pos positions into protocol.Location form.
-func toGobDiagnostic(posToLocation func(start, end token.Pos) (protocol.Location, error), a *analysis.Analyzer, diag analysis.Diagnostic) (gobDiagnostic, error) {
+func toGobDiagnostic(pkg *Package, a *analysis.Analyzer, diag analysis.Diagnostic) (gobDiagnostic, error) {
var fixes []gobSuggestedFix
for _, fix := range diag.SuggestedFixes {
var gobEdits []gobTextEdit
for _, textEdit := range fix.TextEdits {
- loc, err := posToLocation(textEdit.Pos, textEdit.End)
+ loc, err := diagnosticPosToLocation(pkg, false, textEdit.Pos, textEdit.End)
if err != nil {
return gobDiagnostic{}, fmt.Errorf("in SuggestedFixes: %w", err)
}
@@ -1349,7 +1257,10 @@ func toGobDiagnostic(posToLocation func(start, end token.Pos) (protocol.Location
var related []gobRelatedInformation
for _, r := range diag.Related {
- loc, err := posToLocation(r.Pos, r.End)
+ // The position of RelatedInformation may be
+ // within another (dependency) package.
+ const allowDeps = true
+ loc, err := diagnosticPosToLocation(pkg, allowDeps, r.Pos, r.End)
if err != nil {
return gobDiagnostic{}, fmt.Errorf("in Related: %w", err)
}
@@ -1359,7 +1270,7 @@ func toGobDiagnostic(posToLocation func(start, end token.Pos) (protocol.Location
})
}
- loc, err := posToLocation(diag.Pos, diag.End)
+ loc, err := diagnosticPosToLocation(pkg, false, diag.Pos, diag.End)
if err != nil {
return gobDiagnostic{}, err
}
@@ -1387,6 +1298,126 @@ func toGobDiagnostic(posToLocation func(start, end token.Pos) (protocol.Location
}, nil
}
+// diagnosticPosToLocation converts from token.Pos to protocol form, in the
+// context of the specified package and, optionally, its dependencies.
+func diagnosticPosToLocation(pkg *Package, allowDeps bool, start, end token.Pos) (protocol.Location, error) {
+ if end == token.NoPos {
+ end = start
+ }
+
+ fset := pkg.FileSet()
+ tokFile := fset.File(start)
+
+ // Find existing mapper by file name.
+ // (Don't require an exact token.File match
+ // as the analyzer may have re-parsed the file.)
+ var (
+ mapper *protocol.Mapper
+ fixed bool
+ )
+ for _, p := range pkg.CompiledGoFiles() {
+ if p.Tok.Name() == tokFile.Name() {
+ mapper = p.Mapper
+ fixed = p.Fixed() // suppress some assertions after parser recovery
+ break
+ }
+ }
+ // TODO(adonovan): search pkg.AsmFiles too; see #71754.
+ if mapper != nil {
+ // debugging #64547
+ fileStart := token.Pos(tokFile.Base())
+ fileEnd := fileStart + token.Pos(tokFile.Size())
+ if start < fileStart {
+ if !fixed {
+ bug.Reportf("start < start of file")
+ }
+ start = fileStart
+ }
+ if end < start {
+ // This can happen if End is zero (#66683)
+ // or a small positive displacement from zero
+ // due to recursive Node.End() computation.
+ // This usually arises from poor parser recovery
+ // of an incomplete term at EOF.
+ if !fixed {
+ bug.Reportf("end < start of file")
+ }
+ end = fileEnd
+ }
+ if end > fileEnd+1 {
+ if !fixed {
+ bug.Reportf("end > end of file + 1")
+ }
+ end = fileEnd
+ }
+
+ return mapper.PosLocation(tokFile, start, end)
+ }
+
+ // Inv: the positions are not within this package.
+
+ if allowDeps {
+ // Positions in Diagnostic.RelatedInformation may belong to a
+ // dependency package. We cannot accurately map them to
+ // protocol.Location coordinates without a Mapper for the
+ // relevant file, but none exists if the file was loaded from
+ // export data, and we have no means (Snapshot) of loading it.
+ //
+ // So, fall back to approximate conversion to UTF-16:
+ // for non-ASCII text, the column numbers may be wrong.
+ var (
+ startPosn = safetoken.StartPosition(fset, start)
+ endPosn = safetoken.EndPosition(fset, end)
+ )
+ return protocol.Location{
+ URI: protocol.URIFromPath(startPosn.Filename),
+ Range: protocol.Range{
+ Start: protocol.Position{
+ Line: uint32(startPosn.Line - 1),
+ Character: uint32(startPosn.Column - 1),
+ },
+ End: protocol.Position{
+ Line: uint32(endPosn.Line - 1),
+ Character: uint32(endPosn.Column - 1),
+ },
+ },
+ }, nil
+ }
+
+ // The start position was not among the package's parsed
+ // Go files, indicating that the analyzer added new files
+ // to the FileSet.
+ //
+ // For example, the cgocall analyzer re-parses and
+ // type-checks some of the files in a special environment;
+ // and asmdecl and other low-level runtime analyzers call
+ // ReadFile to parse non-Go files.
+ // (This is a supported feature, documented at go/analysis.)
+ //
+ // In principle these files could be:
+ //
+ // - OtherFiles (non-Go files such as asm).
+ // However, we set Pass.OtherFiles=[] because
+ // gopls won't service "diagnose" requests
+ // for non-Go files, so there's no point
+ // reporting diagnostics in them.
+ //
+ // - IgnoredFiles (files tagged for other configs).
+ // However, we set Pass.IgnoredFiles=[] because,
+ // in most cases, zero-config gopls should create
+ // another view that covers these files.
+ //
+ // - Referents of //line directives, as in cgo packages.
+ // The file names in this case are not known a priori.
+ // gopls generally tries to avoid honoring line directives,
+ // but analyzers such as cgocall may honor them.
+ //
+ // In short, it's unclear how this can be reached
+ // other than due to an analyzer bug.
+
+ return protocol.Location{}, bug.Errorf("diagnostic location is not among files of package: %s", tokFile.Name())
+}
+
// effectiveURL computes the effective URL of diag,
// using the algorithm specified at Diagnostic.URL.
func effectiveURL(a *analysis.Analyzer, diag analysis.Diagnostic) string {
diff --git a/gopls/internal/cache/cache.go b/gopls/internal/cache/cache.go
index 9f85846165f..9d6d64c9e71 100644
--- a/gopls/internal/cache/cache.go
+++ b/gopls/internal/cache/cache.go
@@ -105,7 +105,7 @@ type Cache struct {
// our best knowledge of the current file system state.
*memoizedFS
- // modCache holds the
+ // modCache holds the shared goimports state for GOMODCACHE directories
modCache *sharedModCache
}
diff --git a/gopls/internal/cache/constraints.go b/gopls/internal/cache/constraints.go
index 9503abc1ebd..a9a87ae6d4b 100644
--- a/gopls/internal/cache/constraints.go
+++ b/gopls/internal/cache/constraints.go
@@ -9,6 +9,7 @@ import (
"go/build/constraint"
"go/parser"
"go/token"
+ "slices"
)
// isStandaloneFile reports whether a file with the given contents should be
@@ -27,11 +28,9 @@ func isStandaloneFile(src []byte, standaloneTags []string) bool {
found := false
walkConstraints(f, func(c constraint.Expr) bool {
if tag, ok := c.(*constraint.TagExpr); ok {
- for _, t := range standaloneTags {
- if t == tag.Tag {
- found = true
- return false
- }
+ if slices.Contains(standaloneTags, tag.Tag) {
+ found = true
+ return false
}
}
return true
diff --git a/gopls/internal/cache/filterer.go b/gopls/internal/cache/filterer.go
index 13dbd8a1b04..9f911ec9de8 100644
--- a/gopls/internal/cache/filterer.go
+++ b/gopls/internal/cache/filterer.go
@@ -71,8 +71,8 @@ func convertFilterToRegexp(filter string) *regexp.Regexp {
}
var ret strings.Builder
ret.WriteString("^/")
- segs := strings.Split(filter, "/")
- for _, seg := range segs {
+ segs := strings.SplitSeq(filter, "/")
+ for seg := range segs {
// Inv: seg != "" since path is clean.
if seg == "**" {
ret.WriteString(".*")
diff --git a/gopls/internal/cache/load.go b/gopls/internal/cache/load.go
index e15e0cef0b6..b45669b3b79 100644
--- a/gopls/internal/cache/load.go
+++ b/gopls/internal/cache/load.go
@@ -791,7 +791,7 @@ func computeWorkspacePackagesLocked(ctx context.Context, s *Snapshot, meta *meta
func allFilesHaveRealPackages(g *metadata.Graph, mp *metadata.Package) bool {
n := len(mp.CompiledGoFiles)
checkURIs:
- for _, uri := range append(mp.CompiledGoFiles[0:n:n], mp.GoFiles...) {
+ for _, uri := range slices.Concat(mp.CompiledGoFiles[0:n:n], mp.GoFiles) {
for _, id := range g.IDs[uri] {
if !metadata.IsCommandLineArguments(id) {
continue checkURIs
diff --git a/gopls/internal/cache/metadata/cycle_test.go b/gopls/internal/cache/metadata/cycle_test.go
index 09628d881e9..5f935f603c8 100644
--- a/gopls/internal/cache/metadata/cycle_test.go
+++ b/gopls/internal/cache/metadata/cycle_test.go
@@ -5,6 +5,7 @@
package metadata
import (
+ "maps"
"sort"
"strings"
"testing"
@@ -40,11 +41,11 @@ func TestBreakImportCycles(t *testing.T) {
return n
}
if s != "" {
- for _, item := range strings.Split(s, ";") {
+ for item := range strings.SplitSeq(s, ";") {
nodeID, succIDs, ok := strings.Cut(item, "->")
node := makeNode(nodeID)
if ok {
- for _, succID := range strings.Split(succIDs, ",") {
+ for succID := range strings.SplitSeq(succIDs, ",") {
node.DepsByPkgPath[PackagePath(succID)] = PackageID(succID)
}
}
@@ -119,9 +120,7 @@ func TestBreakImportCycles(t *testing.T) {
// Apply updates.
// (parse doesn't have a way to express node deletions,
// but they aren't very interesting.)
- for id, mp := range updates {
- metadata[id] = mp
- }
+ maps.Copy(metadata, updates)
t.Log("updated", format(metadata))
diff --git a/gopls/internal/cache/metadata/graph.go b/gopls/internal/cache/metadata/graph.go
index 716b767e37b..b029b51aa7e 100644
--- a/gopls/internal/cache/metadata/graph.go
+++ b/gopls/internal/cache/metadata/graph.go
@@ -6,6 +6,7 @@ package metadata
import (
"iter"
+ "maps"
"sort"
"strings"
@@ -63,9 +64,7 @@ func (g *Graph) Update(updates map[PackageID]*Package) *Graph {
// Copy pkgs map then apply updates.
pkgs := make(map[PackageID]*Package, len(g.Packages))
- for id, mp := range g.Packages {
- pkgs[id] = mp
- }
+ maps.Copy(pkgs, g.Packages)
for id, mp := range updates {
if mp == nil {
delete(pkgs, id)
diff --git a/gopls/internal/cache/methodsets/methodsets.go b/gopls/internal/cache/methodsets/methodsets.go
index 2387050f2d9..873d2d01289 100644
--- a/gopls/internal/cache/methodsets/methodsets.go
+++ b/gopls/internal/cache/methodsets/methodsets.go
@@ -51,6 +51,7 @@ import (
"sync/atomic"
"golang.org/x/tools/go/types/objectpath"
+ "golang.org/x/tools/gopls/internal/cache/metadata"
"golang.org/x/tools/gopls/internal/util/bug"
"golang.org/x/tools/gopls/internal/util/fingerprint"
"golang.org/x/tools/gopls/internal/util/frob"
@@ -62,14 +63,15 @@ import (
// types in a package in a form that permits assignability queries
// without the type checker.
type Index struct {
- pkg gobPackage
+ pkg gobPackage
+ PkgPath metadata.PackagePath
}
// Decode decodes the given gob-encoded data as an Index.
-func Decode(data []byte) *Index {
+func Decode(pkgpath metadata.PackagePath, data []byte) *Index {
var pkg gobPackage
packageCodec.Decode(data, &pkg)
- return &Index{pkg}
+ return &Index{pkg: pkg, PkgPath: pkgpath}
}
// Encode encodes the receiver as gob-encoded data.
@@ -110,36 +112,61 @@ func KeyOf(t types.Type) (Key, bool) {
// A Result reports a matching type or method in a method-set search.
type Result struct {
- Location Location // location of the type or method
+ TypeName string // name of the named type
+ IsInterface bool // matched type (or method) is abstract
+ Location Location // location of the type or method
// methods only:
PkgPath string // path of declaring package (may differ due to embedding)
ObjectPath objectpath.Path // path of method within declaring package
}
-// Search reports each type that implements (or is implemented by) the
-// type that produced the search key. If methodID is nonempty, only
-// that method of each type is reported.
+// TypeRelation indicates the direction of subtyping relation,
+// if any, between two types.
+//
+// It is a bitset, so that clients of Implementations may use
+// Supertype|Subtype to request an undirected match.
+type TypeRelation int8
+
+const (
+ Supertype TypeRelation = 0x1
+ Subtype TypeRelation = 0x2
+)
+
+// Search reports each type that implements (Supertype ∈ want) or is
+// implemented by (Subtype ∈ want) the type that produced the search key.
+//
+// If method is non-nil, only that method of each type is reported.
//
// The result does not include the error.Error method.
// TODO(adonovan): give this special case a more systematic treatment.
-func (index *Index) Search(key Key, method *types.Func) []Result {
+func (index *Index) Search(key Key, want TypeRelation, method *types.Func) []Result {
var results []Result
for _, candidate := range index.pkg.MethodSets {
- // Traditionally this feature doesn't report
- // interface/interface elements of the relation.
- // I think that's a mistake.
- // TODO(adonovan): UX: change it, here and in the local implementation.
- if candidate.IsInterface && key.mset.IsInterface {
- continue
+ // Test the direction of the relation.
+ // The client may request either direction or both
+ // (e.g. when the client is References),
+ // and the Result reports each test independently;
+ // both tests succeed when comparing identical
+ // interface types.
+ var got TypeRelation
+ if want&Subtype != 0 && implements(candidate, key.mset) {
+ got |= Subtype
}
-
- if !implements(candidate, key.mset) && !implements(key.mset, candidate) {
+ if want&Supertype != 0 && implements(key.mset, candidate) {
+ got |= Supertype
+ }
+ if got == 0 {
continue
}
+ typeName := index.pkg.Strings[candidate.TypeName]
if method == nil {
- results = append(results, Result{Location: index.location(candidate.Posn)})
+ results = append(results, Result{
+ TypeName: typeName,
+ IsInterface: candidate.IsInterface,
+ Location: index.location(candidate.Posn),
+ })
} else {
for _, m := range candidate.Methods {
if m.ID == method.Id() {
@@ -154,9 +181,11 @@ func (index *Index) Search(key Key, method *types.Func) []Result {
}
results = append(results, Result{
- Location: index.location(m.Posn),
- PkgPath: index.pkg.Strings[m.PkgPath],
- ObjectPath: objectpath.Path(index.pkg.Strings[m.ObjectPath]),
+ TypeName: typeName,
+ IsInterface: candidate.IsInterface,
+ Location: index.location(m.Posn),
+ PkgPath: index.pkg.Strings[m.PkgPath],
+ ObjectPath: objectpath.Path(index.pkg.Strings[m.ObjectPath]),
})
break
}
@@ -285,6 +314,7 @@ func (b *indexBuilder) build(fset *token.FileSet, pkg *types.Package) *Index {
for _, name := range scope.Names() {
if tname, ok := scope.Lookup(name).(*types.TypeName); ok && !tname.IsAlias() {
if mset := methodSetInfo(tname.Type(), setIndexInfo); mset.Mask != 0 {
+ mset.TypeName = b.string(name)
mset.Posn = objectPos(tname)
// Only record types with non-trivial method sets.
b.MethodSets = append(b.MethodSets, mset)
@@ -292,7 +322,10 @@ func (b *indexBuilder) build(fset *token.FileSet, pkg *types.Package) *Index {
}
}
- return &Index{pkg: b.gobPackage}
+ return &Index{
+ pkg: b.gobPackage,
+ PkgPath: metadata.PackagePath(pkg.Path()),
+ }
}
// string returns a small integer that encodes the string.
@@ -370,6 +403,7 @@ type gobPackage struct {
// A gobMethodSet records the method set of a single type.
type gobMethodSet struct {
+ TypeName int // name (string index) of the package-level type
Posn gobPosition
IsInterface bool
Tricky bool // at least one method is tricky; fingerprint must be parsed + unified
diff --git a/gopls/internal/cache/mod.go b/gopls/internal/cache/mod.go
index f6dd22754cc..ddbe516f165 100644
--- a/gopls/internal/cache/mod.go
+++ b/gopls/internal/cache/mod.go
@@ -13,6 +13,7 @@ import (
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
+ "golang.org/x/tools/go/packages"
"golang.org/x/tools/gopls/internal/file"
"golang.org/x/tools/gopls/internal/label"
"golang.org/x/tools/gopls/internal/protocol"
@@ -25,6 +26,7 @@ import (
type ParsedModule struct {
URI protocol.DocumentURI
File *modfile.File
+ ReplaceMap map[module.Version]module.Version
Mapper *protocol.Mapper
ParseErrors []*Diagnostic
}
@@ -98,10 +100,19 @@ func parseModImpl(ctx context.Context, fh file.Handle) (*ParsedModule, error) {
})
}
}
+
+ replaceMap := make(map[module.Version]module.Version)
+ if parseErr == nil {
+ for _, rep := range file.Replace {
+ replaceMap[rep.Old] = rep.New
+ }
+ }
+
return &ParsedModule{
URI: fh.URI(),
Mapper: m,
File: file,
+ ReplaceMap: replaceMap,
ParseErrors: parseErrors,
}, parseErr
}
@@ -487,3 +498,31 @@ func findModuleReference(mf *modfile.File, ver module.Version) *modfile.Line {
}
return nil
}
+
+// ResolvedVersion returns the version used for a module, which considers replace directive.
+func ResolvedVersion(module *packages.Module) string {
+ // don't visit replace recursively as src/cmd/go/internal/modinfo/info.go
+ // visits replace field only once.
+ if module.Replace != nil {
+ return module.Replace.Version
+ }
+ return module.Version
+}
+
+// ResolvedPath returns the the module path, which considers replace directive.
+func ResolvedPath(module *packages.Module) string {
+ if module.Replace != nil {
+ return module.Replace.Path
+ }
+ return module.Path
+}
+
+// ResolvedString returns a representation of the Version suitable for logging
+// (Path@Version, or just Path if Version is empty),
+// which considers replace directive.
+func ResolvedString(module *packages.Module) string {
+ if ResolvedVersion(module) == "" {
+ ResolvedPath(module)
+ }
+ return ResolvedPath(module) + "@" + ResolvedVersion(module)
+}
diff --git a/gopls/internal/cache/mod_vuln.go b/gopls/internal/cache/mod_vuln.go
index a48b18e4ba4..5b7d679fa48 100644
--- a/gopls/internal/cache/mod_vuln.go
+++ b/gopls/internal/cache/mod_vuln.go
@@ -126,7 +126,6 @@ func modVulnImpl(ctx context.Context, snapshot *Snapshot) (*vulncheck.Result, er
var group errgroup.Group
group.SetLimit(10) // limit govulncheck api runs
for _, mps := range packagesByModule {
- mps := mps
group.Go(func() error {
effectiveModule := stdlibModule
if m := mps[0].Module; m != nil {
diff --git a/gopls/internal/cache/parse_cache_test.go b/gopls/internal/cache/parse_cache_test.go
index fe0548aa20d..4e3a7cf32b7 100644
--- a/gopls/internal/cache/parse_cache_test.go
+++ b/gopls/internal/cache/parse_cache_test.go
@@ -195,9 +195,9 @@ func TestParseCache_Duplicates(t *testing.T) {
func dummyFileHandles(n int) []file.Handle {
var fhs []file.Handle
- for i := 0; i < n; i++ {
+ for i := range n {
uri := protocol.DocumentURI(fmt.Sprintf("file:///_%d", i))
- src := []byte(fmt.Sprintf("package p\nvar _ = %d", i))
+ src := fmt.Appendf(nil, "package p\nvar _ = %d", i)
fhs = append(fhs, makeFakeFileHandle(uri, src))
}
return fhs
diff --git a/gopls/internal/cache/parsego/parse.go b/gopls/internal/cache/parsego/parse.go
index 4b37816caff..bc5483fc166 100644
--- a/gopls/internal/cache/parsego/parse.go
+++ b/gopls/internal/cache/parsego/parse.go
@@ -49,7 +49,7 @@ const (
// Parse parses a buffer of Go source, repairing the tree if necessary.
//
// The provided ctx is used only for logging.
-func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, src []byte, mode parser.Mode, purgeFuncBodies bool) (res *File, fixes []fixType) {
+func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, src []byte, mode parser.Mode, purgeFuncBodies bool) (res *File, fixes []FixType) {
if purgeFuncBodies {
src = astutil.PurgeFuncBodies(src)
}
@@ -81,7 +81,7 @@ func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, s
fixes = append(fixes, astFixes...)
}
- for i := 0; i < 10; i++ {
+ for i := range 10 {
// Fix certain syntax errors that render the file unparseable.
newSrc, srcFix := fixSrc(file, tok, src)
if newSrc == nil {
@@ -147,13 +147,13 @@ func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, s
//
// If fixAST returns true, the resulting AST is considered "fixed", meaning
// positions have been mangled, and type checker errors may not make sense.
-func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []fixType) {
+func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []FixType) {
var err error
walkASTWithParent(n, func(n, parent ast.Node) bool {
switch n := n.(type) {
case *ast.BadStmt:
if fixDeferOrGoStmt(n, parent, tok, src) {
- fixes = append(fixes, fixedDeferOrGo)
+ fixes = append(fixes, FixedDeferOrGo)
// Recursively fix in our fixed node.
moreFixes := fixAST(parent, tok, src)
fixes = append(fixes, moreFixes...)
@@ -163,7 +163,7 @@ func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []fixType) {
return false
case *ast.BadExpr:
if fixArrayType(n, parent, tok, src) {
- fixes = append(fixes, fixedArrayType)
+ fixes = append(fixes, FixedArrayType)
// Recursively fix in our fixed node.
moreFixes := fixAST(parent, tok, src)
fixes = append(fixes, moreFixes...)
@@ -177,7 +177,7 @@ func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []fixType) {
// for i := foo
//
if fixInitStmt(n, parent, tok, src) {
- fixes = append(fixes, fixedInit)
+ fixes = append(fixes, FixedInit)
}
return false
case *ast.SelectorExpr:
@@ -186,7 +186,7 @@ func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []fixType) {
// foo.var<> // want to complete to "foo.variance"
//
if fixPhantomSelector(n, tok, src) {
- fixes = append(fixes, fixedPhantomSelector)
+ fixes = append(fixes, FixedPhantomSelector)
}
return true
@@ -196,7 +196,7 @@ func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []fixType) {
// Adjust closing curly brace of empty switch/select
// statements so we can complete inside them.
if fixEmptySwitch(n, tok, src) {
- fixes = append(fixes, fixedEmptySwitch)
+ fixes = append(fixes, FixedEmptySwitch)
}
}
@@ -235,24 +235,24 @@ func walkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
// TODO(rfindley): revert this intrumentation once we're certain the crash in
// #59097 is fixed.
-type fixType int
+type FixType int
const (
- noFix fixType = iota
- fixedCurlies
- fixedDanglingSelector
- fixedDeferOrGo
- fixedArrayType
- fixedInit
- fixedPhantomSelector
- fixedEmptySwitch
+ noFix FixType = iota
+ FixedCurlies
+ FixedDanglingSelector
+ FixedDeferOrGo
+ FixedArrayType
+ FixedInit
+ FixedPhantomSelector
+ FixedEmptySwitch
)
// fixSrc attempts to modify the file's source code to fix certain
// syntax errors that leave the rest of the file unparsed.
//
// fixSrc returns a non-nil result if and only if a fix was applied.
-func fixSrc(f *ast.File, tf *token.File, src []byte) (newSrc []byte, fix fixType) {
+func fixSrc(f *ast.File, tf *token.File, src []byte) (newSrc []byte, fix FixType) {
walkASTWithParent(f, func(n, parent ast.Node) bool {
if newSrc != nil {
return false
@@ -262,12 +262,12 @@ func fixSrc(f *ast.File, tf *token.File, src []byte) (newSrc []byte, fix fixType
case *ast.BlockStmt:
newSrc = fixMissingCurlies(f, n, parent, tf, src)
if newSrc != nil {
- fix = fixedCurlies
+ fix = FixedCurlies
}
case *ast.SelectorExpr:
newSrc = fixDanglingSelector(n, tf, src)
if newSrc != nil {
- fix = fixedDanglingSelector
+ fix = FixedDanglingSelector
}
}
@@ -903,10 +903,7 @@ func offsetPositions(tok *token.File, n ast.Node, offset token.Pos) {
//
// TODO(golang/go#64335): this is a hack, because our fixes should not
// produce positions that overflow (but they do: golang/go#64488).
- pos := f.Int() + int64(offset)
- if pos < fileBase {
- pos = fileBase
- }
+ pos := max(f.Int()+int64(offset), fileBase)
if pos > fileEnd {
pos = fileEnd
}
diff --git a/gopls/internal/cache/parsego/parse_test.go b/gopls/internal/cache/parsego/parse_test.go
index c64125427b1..db78b596042 100644
--- a/gopls/internal/cache/parsego/parse_test.go
+++ b/gopls/internal/cache/parsego/parse_test.go
@@ -6,12 +6,17 @@ package parsego_test
import (
"context"
+ "fmt"
"go/ast"
+ "go/parser"
"go/token"
+ "reflect"
+ "slices"
"testing"
"golang.org/x/tools/gopls/internal/cache/parsego"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ "golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/tokeninternal"
)
@@ -44,3 +49,319 @@ func _() {
return true
})
}
+
+func TestFixGoAndDefer(t *testing.T) {
+ var testCases = []struct {
+ source string
+ fixes []parsego.FixType
+ wantFix string
+ }{
+ {source: "", fixes: nil}, // keyword alone
+ {source: "a.b(", fixes: nil},
+ {source: "a.b()", fixes: nil},
+ {source: "func {", fixes: nil},
+ {
+ source: "f",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo},
+ wantFix: "f()",
+ },
+ {
+ source: "func",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo},
+ wantFix: "(func())()",
+ },
+ {
+ source: "func {}",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo},
+ wantFix: "(func())()",
+ },
+ {
+ source: "func {}(",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo},
+ wantFix: "(func())()",
+ },
+ {
+ source: "func {}()",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo},
+ wantFix: "(func())()",
+ },
+ {
+ source: "a.",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo, parsego.FixedDanglingSelector, parsego.FixedDeferOrGo},
+ wantFix: "a._()",
+ },
+ {
+ source: "a.b",
+ fixes: []parsego.FixType{parsego.FixedDeferOrGo},
+ wantFix: "a.b()",
+ },
+ }
+
+ for _, keyword := range []string{"go", "defer"} {
+ for _, tc := range testCases {
+ source := fmt.Sprintf("%s %s", keyword, tc.source)
+ t.Run(source, func(t *testing.T) {
+ src := filesrc(source)
+ pgf, fixes := parsego.Parse(context.Background(), token.NewFileSet(), "file://foo.go", src, parsego.Full, false)
+ if !slices.Equal(fixes, tc.fixes) {
+ t.Fatalf("got %v want %v", fixes, tc.fixes)
+ }
+ if tc.fixes == nil {
+ return
+ }
+
+ fset := tokeninternal.FileSetFor(pgf.Tok)
+ inspect(t, pgf, func(stmt ast.Stmt) {
+ var call *ast.CallExpr
+ switch stmt := stmt.(type) {
+ case *ast.DeferStmt:
+ call = stmt.Call
+ case *ast.GoStmt:
+ call = stmt.Call
+ default:
+ return
+ }
+
+ if got := analysisinternal.Format(fset, call); got != tc.wantFix {
+ t.Fatalf("got %v want %v", got, tc.wantFix)
+ }
+ })
+ })
+ }
+ }
+}
+
+// TestFixInit tests the init stmt after if/for/switch which is put under cond after parsing
+// will be fixed and moved to Init.
+func TestFixInit(t *testing.T) {
+ var testCases = []struct {
+ name string
+ source string
+ fixes []parsego.FixType
+ wantInitFix string
+ }{
+ {
+ name: "simple define",
+ source: "i := 0",
+ fixes: []parsego.FixType{parsego.FixedInit},
+ wantInitFix: "i := 0",
+ },
+ {
+ name: "simple assign",
+ source: "i = 0",
+ fixes: []parsego.FixType{parsego.FixedInit},
+ wantInitFix: "i = 0",
+ },
+ {
+ name: "define with function call",
+ source: "i := f()",
+ fixes: []parsego.FixType{parsego.FixedInit},
+ wantInitFix: "i := f()",
+ },
+ {
+ name: "assign with function call",
+ source: "i = f()",
+ fixes: []parsego.FixType{parsego.FixedInit},
+ wantInitFix: "i = f()",
+ },
+ {
+ name: "assign with receiving chan",
+ source: "i = <-ch",
+ fixes: []parsego.FixType{parsego.FixedInit},
+ wantInitFix: "i = <-ch",
+ },
+
+ // fixInitStmt won't fix the following cases.
+ {
+ name: "call in if",
+ source: `fmt.Println("helloworld")`,
+ fixes: nil,
+ },
+ {
+ name: "receive chan",
+ source: `<- ch`,
+ fixes: nil,
+ },
+ }
+
+ // currently, switch will leave its Tag empty after fix because it allows empty,
+ // and if and for will leave an underscore in Cond.
+ getWantCond := func(keyword string) string {
+ if keyword == "switch" {
+ return ""
+ }
+ return "_"
+ }
+
+ for _, keyword := range []string{"if", "for", "switch"} {
+ for _, tc := range testCases {
+ caseName := fmt.Sprintf("%s %s", keyword, tc.name)
+ t.Run(caseName, func(t *testing.T) {
+ // the init stmt is treated as a cond.
+ src := filesrc(fmt.Sprintf("%s %s {}", keyword, tc.source))
+ pgf, fixes := parsego.Parse(context.Background(), token.NewFileSet(), "file://foo.go", src, parsego.Full, false)
+ if !slices.Equal(fixes, tc.fixes) {
+ t.Fatalf("TestFixArrayType(): got %v want %v", fixes, tc.fixes)
+ }
+ if tc.fixes == nil {
+ return
+ }
+
+ // ensure the init stmt is parsed to a BadExpr.
+ ensureSource(t, src, func(bad *ast.BadExpr) {})
+
+ info := func(n ast.Node, wantStmt string) (init ast.Stmt, cond ast.Expr, has bool) {
+ switch wantStmt {
+ case "if":
+ if e, ok := n.(*ast.IfStmt); ok {
+ return e.Init, e.Cond, true
+ }
+ case "switch":
+ if e, ok := n.(*ast.SwitchStmt); ok {
+ return e.Init, e.Tag, true
+ }
+ case "for":
+ if e, ok := n.(*ast.ForStmt); ok {
+ return e.Init, e.Cond, true
+ }
+ }
+ return nil, nil, false
+ }
+ fset := tokeninternal.FileSetFor(pgf.Tok)
+ inspect(t, pgf, func(n ast.Stmt) {
+ if init, cond, ok := info(n, keyword); ok {
+ if got := analysisinternal.Format(fset, init); got != tc.wantInitFix {
+ t.Fatalf("%s: Init got %v want %v", tc.source, got, tc.wantInitFix)
+ }
+
+ wantCond := getWantCond(keyword)
+ if got := analysisinternal.Format(fset, cond); got != wantCond {
+ t.Fatalf("%s: Cond got %v want %v", tc.source, got, wantCond)
+ }
+ }
+ })
+ })
+ }
+ }
+}
+
+func TestFixPhantomSelector(t *testing.T) {
+ wantFixes := []parsego.FixType{parsego.FixedPhantomSelector}
+ var testCases = []struct {
+ source string
+ fixes []parsego.FixType
+ }{
+ {source: "a.break", fixes: wantFixes},
+ {source: "_.break", fixes: wantFixes},
+ {source: "a.case", fixes: wantFixes},
+ {source: "a.chan", fixes: wantFixes},
+ {source: "a.const", fixes: wantFixes},
+ {source: "a.continue", fixes: wantFixes},
+ {source: "a.default", fixes: wantFixes},
+ {source: "a.defer", fixes: wantFixes},
+ {source: "a.else", fixes: wantFixes},
+ {source: "a.fallthrough", fixes: wantFixes},
+ {source: "a.for", fixes: wantFixes},
+ {source: "a.func", fixes: wantFixes},
+ {source: "a.go", fixes: wantFixes},
+ {source: "a.goto", fixes: wantFixes},
+ {source: "a.if", fixes: wantFixes},
+ {source: "a.import", fixes: wantFixes},
+ {source: "a.interface", fixes: wantFixes},
+ {source: "a.map", fixes: wantFixes},
+ {source: "a.package", fixes: wantFixes},
+ {source: "a.range", fixes: wantFixes},
+ {source: "a.return", fixes: wantFixes},
+ {source: "a.select", fixes: wantFixes},
+ {source: "a.struct", fixes: wantFixes},
+ {source: "a.switch", fixes: wantFixes},
+ {source: "a.type", fixes: wantFixes},
+ {source: "a.var", fixes: wantFixes},
+
+ {source: "break.break"},
+ {source: "a.BREAK"},
+ {source: "a.break_"},
+ {source: "a.breaka"},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.source, func(t *testing.T) {
+ src := filesrc(tc.source)
+ pgf, fixes := parsego.Parse(context.Background(), token.NewFileSet(), "file://foo.go", src, parsego.Full, false)
+ if !slices.Equal(fixes, tc.fixes) {
+ t.Fatalf("got %v want %v", fixes, tc.fixes)
+ }
+
+ // some fixes don't fit the fix scenario, but we want to confirm it.
+ if fixes == nil {
+ return
+ }
+
+ // ensure the selector has been converted to underscore by parser.
+ ensureSource(t, src, func(sel *ast.SelectorExpr) {
+ if sel.Sel.Name != "_" {
+ t.Errorf("%s: the input doesn't cause a blank selector after parser", tc.source)
+ }
+ })
+
+ fset := tokeninternal.FileSetFor(pgf.Tok)
+ inspect(t, pgf, func(sel *ast.SelectorExpr) {
+ // the fix should restore the selector as is.
+ if got, want := fmt.Sprintf("%s", analysisinternal.Format(fset, sel)), tc.source; got != want {
+ t.Fatalf("got %v want %v", got, want)
+ }
+ })
+ })
+ }
+}
+
+// inspect helps to go through each node of pgf and trigger checkFn if the type matches T.
+func inspect[T ast.Node](t *testing.T, pgf *parsego.File, checkFn func(n T)) {
+ fset := tokeninternal.FileSetFor(pgf.Tok)
+ var visited bool
+ ast.Inspect(pgf.File, func(node ast.Node) bool {
+ if node != nil {
+ posn := safetoken.StartPosition(fset, node.Pos())
+ if !posn.IsValid() {
+ t.Fatalf("invalid position for %T (%v): %v not in [%d, %d]", node, node, node.Pos(), pgf.Tok.Base(), pgf.Tok.Base()+pgf.Tok.Size())
+ }
+ if n, ok := node.(T); ok {
+ visited = true
+ checkFn(n)
+ }
+ }
+ return true
+ })
+ if !visited {
+ var n T
+ t.Fatalf("got no %s node but want at least one", reflect.TypeOf(n))
+ }
+}
+
+// ensureSource helps to parse src into an ast.File by go/parser and trigger checkFn if the type matches T.
+func ensureSource[T ast.Node](t *testing.T, src []byte, checkFn func(n T)) {
+ // tolerate error as usually the src is problematic.
+ originFile, _ := parser.ParseFile(token.NewFileSet(), "file://foo.go", src, parsego.Full)
+ var visited bool
+ ast.Inspect(originFile, func(node ast.Node) bool {
+ if n, ok := node.(T); ok {
+ visited = true
+ checkFn(n)
+ }
+ return true
+ })
+
+ if !visited {
+ var n T
+ t.Fatalf("got no %s node but want at least one", reflect.TypeOf(n))
+ }
+}
+
+func filesrc(expressions string) []byte {
+ const srcTmpl = `package foo
+
+func _() {
+ %s
+}`
+ return fmt.Appendf(nil, srcTmpl, expressions)
+}
diff --git a/gopls/internal/cache/port_test.go b/gopls/internal/cache/port_test.go
index a92056a9c22..5d0c5d4a50f 100644
--- a/gopls/internal/cache/port_test.go
+++ b/gopls/internal/cache/port_test.go
@@ -46,7 +46,6 @@ func TestMatchingPortsStdlib(t *testing.T) {
var g errgroup.Group
packages.Visit(pkgs, nil, func(pkg *packages.Package) {
for _, f := range pkg.CompiledGoFiles {
- f := f
g.Go(func() error {
content, err := os.ReadFile(f)
// We report errors via t.Error, not by returning,
@@ -118,7 +117,7 @@ func getFileID(filename string) (FileID, time.Time, error) {
}
`
fh := makeFakeFileHandle("file:///path/to/test/file.go", []byte(src))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = matchingPreferredPorts(b, fh, true)
}
}
diff --git a/gopls/internal/cache/session.go b/gopls/internal/cache/session.go
index c46fc78b975..f0d8f062138 100644
--- a/gopls/internal/cache/session.go
+++ b/gopls/internal/cache/session.go
@@ -139,11 +139,18 @@ func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot
}
view, snapshot, release := s.createView(ctx, def)
s.views = append(s.views, view)
- // we always need to drop the view map
- s.viewMap = make(map[protocol.DocumentURI]*View)
+ s.viewMap[protocol.Clean(folder.Dir)] = view
return view, snapshot, release, nil
}
+// HasView checks whether the uri's view exists.
+func (s *Session) HasView(uri protocol.DocumentURI) bool {
+ s.viewMu.Lock()
+ defer s.viewMu.Unlock()
+ _, ok := s.viewMap[protocol.Clean(uri)]
+ return ok
+}
+
// createView creates a new view, with an initial snapshot that retains the
// supplied context, detached from events and cancelation.
//
@@ -175,7 +182,7 @@ func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *
// Note that the logic below doesn't handle the case where uri ==
// v.folder.Dir, because there is no point in excluding the entire
// workspace folder!
- if rel := strings.TrimPrefix(uri, dirPrefix); rel != uri {
+ if rel, ok := strings.CutPrefix(uri, dirPrefix); ok {
return !pathIncluded(rel)
}
return false
@@ -389,7 +396,7 @@ func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Sn
// View is shut down. Forget this association.
s.viewMu.Lock()
if s.viewMap[uri] == v {
- delete(s.viewMap, uri)
+ delete(s.viewMap, protocol.Clean(uri))
}
s.viewMu.Unlock()
}
@@ -478,7 +485,7 @@ func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*
// (as in golang/go#60776).
v = relevantViews[0]
}
- s.viewMap[uri] = v // may be nil
+ s.viewMap[protocol.Clean(uri)] = v // may be nil
}
return v, nil
}
diff --git a/gopls/internal/cache/snapshot.go b/gopls/internal/cache/snapshot.go
index 754389c7008..f936bbfc458 100644
--- a/gopls/internal/cache/snapshot.go
+++ b/gopls/internal/cache/snapshot.go
@@ -608,7 +608,7 @@ func (s *Snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methods
pre := func(i int, ph *packageHandle) bool {
data, err := filecache.Get(methodSetsKind, ph.key)
if err == nil { // hit
- indexes[i] = methodsets.Decode(data)
+ indexes[i] = methodsets.Decode(ph.mp.PkgPath, data)
return false
} else if err != filecache.ErrNotFound {
event.Error(ctx, "reading methodsets from filecache", err)
@@ -916,10 +916,12 @@ func (s *Snapshot) watchSubdirs() bool {
// requirements that client names do not change. We should update the VS
// Code extension to set a default value of "subdirWatchPatterns" to "on",
// so that this workaround is only temporary.
- if s.Options().ClientInfo.Name == "Visual Studio Code" {
+ switch s.Options().ClientInfo.Name {
+ case "Visual Studio Code", "Visual Studio Code - Insiders":
return true
+ default:
+ return false
}
- return false
default:
bug.Reportf("invalid subdirWatchPatterns: %q", p)
return false
diff --git a/gopls/internal/cache/source.go b/gopls/internal/cache/source.go
index 7946b9746ab..047cc3971d8 100644
--- a/gopls/internal/cache/source.go
+++ b/gopls/internal/cache/source.go
@@ -103,7 +103,7 @@ func (s *goplsSource) ResolveReferences(ctx context.Context, filename string, mi
}
dbgpr := func(hdr string, v []*imports.Result) {
- for i := 0; i < len(v); i++ {
+ for i := range v {
log.Printf("%s%d %+v %+v", hdr, i, v[i].Import, v[i].Package)
}
}
diff --git a/gopls/internal/cache/typerefs/packageset.go b/gopls/internal/cache/typerefs/packageset.go
index f4f7c94f712..af495d1573c 100644
--- a/gopls/internal/cache/typerefs/packageset.go
+++ b/gopls/internal/cache/typerefs/packageset.go
@@ -124,7 +124,7 @@ func (s *PackageSet) Contains(id metadata.PackageID) bool {
// Elems calls f for each element of the set in ascending order.
func (s *PackageSet) Elems(f func(IndexID)) {
for i, v := range moremaps.Sorted(s.sparse) {
- for b := 0; b < blockSize; b++ {
+ for b := range blockSize {
if (v & (1 << b)) != 0 {
f(IndexID(i*blockSize + b))
}
diff --git a/gopls/internal/cache/typerefs/pkggraph_test.go b/gopls/internal/cache/typerefs/pkggraph_test.go
index 20e34ce1aa9..f205da85b35 100644
--- a/gopls/internal/cache/typerefs/pkggraph_test.go
+++ b/gopls/internal/cache/typerefs/pkggraph_test.go
@@ -84,7 +84,6 @@ func BuildPackageGraph(ctx context.Context, meta metadata.Source, ids []metadata
var eg errgroup.Group
eg.SetLimit(workers)
for _, id := range ids {
- id := id
eg.Go(func() error {
_, err := g.Package(ctx, id)
return err
diff --git a/gopls/internal/cache/typerefs/pkgrefs_test.go b/gopls/internal/cache/typerefs/pkgrefs_test.go
index 3f9a976ccf7..ce297e4380b 100644
--- a/gopls/internal/cache/typerefs/pkgrefs_test.go
+++ b/gopls/internal/cache/typerefs/pkgrefs_test.go
@@ -12,7 +12,7 @@ import (
"go/token"
"go/types"
"os"
- "sort"
+ "slices"
"strings"
"sync"
"testing"
@@ -81,9 +81,7 @@ func TestBuildPackageGraph(t *testing.T) {
for id := range exports {
ids = append(ids, id)
}
- sort.Slice(ids, func(i, j int) bool {
- return ids[i] < ids[j]
- })
+ slices.Sort(ids)
t0 = time.Now()
g, err := BuildPackageGraph(ctx, meta, ids, newParser().parse)
@@ -259,9 +257,8 @@ func BenchmarkBuildPackageGraph(b *testing.B) {
for id := range exports {
ids = append(ids, id)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildPackageGraph(ctx, meta, ids, newParser().parse)
if err != nil {
b.Fatal(err)
diff --git a/gopls/internal/cache/view.go b/gopls/internal/cache/view.go
index 6bb0ae8edeb..4e8375a77db 100644
--- a/gopls/internal/cache/view.go
+++ b/gopls/internal/cache/view.go
@@ -473,7 +473,7 @@ func (v *View) filterFunc() func(protocol.DocumentURI) bool {
gomodcache := v.folder.Env.GOMODCACHE
var filters []string
filters = append(filters, v.folder.Options.DirectoryFilters...)
- if pref := strings.TrimPrefix(gomodcache, folderDir); pref != gomodcache {
+ if pref, ok := strings.CutPrefix(gomodcache, folderDir); ok {
modcacheFilter := "-" + strings.TrimPrefix(filepath.ToSlash(pref), "/")
filters = append(filters, modcacheFilter)
}
@@ -550,7 +550,7 @@ func newIgnoreFilter(dirs []string) *ignoreFilter {
func (f *ignoreFilter) ignored(filename string) bool {
for _, prefix := range f.prefixes {
- if suffix := strings.TrimPrefix(filename, prefix); suffix != filename {
+ if suffix, ok := strings.CutPrefix(filename, prefix); ok {
if checkIgnored(suffix) {
return true
}
@@ -567,7 +567,7 @@ func (f *ignoreFilter) ignored(filename string) bool {
func checkIgnored(suffix string) bool {
// Note: this could be further optimized by writing a HasSegment helper, a
// segment-boundary respecting variant of strings.Contains.
- for _, component := range strings.Split(suffix, string(filepath.Separator)) {
+ for component := range strings.SplitSeq(suffix, string(filepath.Separator)) {
if len(component) == 0 {
continue
}
diff --git a/gopls/internal/clonetest/clonetest.go b/gopls/internal/clonetest/clonetest.go
index 3542476ae09..773bc170fe7 100644
--- a/gopls/internal/clonetest/clonetest.go
+++ b/gopls/internal/clonetest/clonetest.go
@@ -13,6 +13,7 @@ package clonetest
import (
"fmt"
"reflect"
+ "slices"
)
// NonZero returns a T set to some appropriate nonzero value:
@@ -36,11 +37,9 @@ func NonZero[T any]() T {
// nonZeroValue returns a non-zero, addressable value of the given type.
func nonZeroValue(t reflect.Type, seen []reflect.Type) reflect.Value {
- for _, t2 := range seen {
- if t == t2 {
- // Cycle: return the zero value.
- return reflect.Zero(t)
- }
+ if slices.Contains(seen, t) {
+ // Cycle: return the zero value.
+ return reflect.Zero(t)
}
seen = append(seen, t)
v := reflect.New(t).Elem()
diff --git a/gopls/internal/cmd/codeaction.go b/gopls/internal/cmd/codeaction.go
index 2096a153681..6931af37d40 100644
--- a/gopls/internal/cmd/codeaction.go
+++ b/gopls/internal/cmd/codeaction.go
@@ -142,7 +142,7 @@ func (cmd *codeaction) Run(ctx context.Context, args ...string) error {
// Request code actions of the desired kinds.
var kinds []protocol.CodeActionKind
if cmd.Kind != "" {
- for _, kind := range strings.Split(cmd.Kind, ",") {
+ for kind := range strings.SplitSeq(cmd.Kind, ",") {
kinds = append(kinds, protocol.CodeActionKind(kind))
}
} else {
diff --git a/gopls/internal/debug/trace.go b/gopls/internal/debug/trace.go
index e6ff9697b67..d80a32eecbe 100644
--- a/gopls/internal/debug/trace.go
+++ b/gopls/internal/debug/trace.go
@@ -11,6 +11,7 @@ import (
"html/template"
"net/http"
"runtime/trace"
+ "slices"
"sort"
"strings"
"sync"
@@ -271,7 +272,7 @@ func (t *traces) addRecentLocked(span *traceSpan, start bool) {
// as Go's GC cannot collect the ever-growing unused prefix.
// So, compact it periodically.
if t.recentEvictions%maxRecent == 0 {
- t.recent = append([]spanStartEnd(nil), t.recent...)
+ t.recent = slices.Clone(t.recent)
}
}
}
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
index 0852870ba41..37a996950be 100644
--- a/gopls/internal/doc/api.json
+++ b/gopls/internal/doc/api.json
@@ -411,663 +411,1607 @@
"ValueType": "bool",
"Keys": [
{
- "Name": "\"appends\"",
- "Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.",
- "Default": "true",
+ "Name": "\"QF1001\"",
+ "Doc": "Apply De Morgan's law\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"asmdecl\"",
- "Doc": "report mismatches between assembly files and Go declarations",
+ "Name": "\"QF1002\"",
+ "Doc": "Convert untagged switch to tagged switch\n\nAn untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n switch {\n case x == 1 || x == 2, x == 3:\n ...\n case x == 4:\n ...\n default:\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2, 3:\n ...\n case 4:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"assign\"",
- "Doc": "check for useless assignments\n\nThis checker reports assignments of the form x = x or a[i] = a[i].\nThese are almost always useless, and even when they aren't they are\nusually a mistake.",
+ "Name": "\"QF1003\"",
+ "Doc": "Convert if/else-if chain to tagged switch\n\nA series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n if x == 1 || x == 2 {\n ...\n } else if x == 3 {\n ...\n } else {\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2:\n ...\n case 3:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"atomic\"",
- "Doc": "check for common mistakes using the sync/atomic package\n\nThe atomic checker looks for assignment statements of the form:\n\n\tx = atomic.AddUint64(\u0026x, 1)\n\nwhich are not atomic.",
+ "Name": "\"QF1004\"",
+ "Doc": "Use strings.ReplaceAll instead of strings.Replace with n == -1\n\nAvailable since\n 2021.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"atomicalign\"",
- "Doc": "check for non-64-bits-aligned arguments to sync/atomic functions",
- "Default": "true",
+ "Name": "\"QF1005\"",
+ "Doc": "Expand call to math.Pow\n\nSome uses of math.Pow can be simplified to basic multiplication.\n\nBefore:\n\n math.Pow(x, 2)\n\nAfter:\n\n x * x\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"bools\"",
- "Doc": "check for common mistakes involving boolean operators",
- "Default": "true",
+ "Name": "\"QF1006\"",
+ "Doc": "Lift if+break into loop condition\n\nBefore:\n\n for {\n if done {\n break\n }\n ...\n }\n\nAfter:\n\n for !done {\n ...\n }\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"buildtag\"",
- "Doc": "check //go:build and // +build directives",
- "Default": "true",
+ "Name": "\"QF1007\"",
+ "Doc": "Merge conditional assignment into variable declaration\n\nBefore:\n\n x := false\n if someCondition {\n x = true\n }\n\nAfter:\n\n x := someCondition\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"cgocall\"",
- "Doc": "detect some violations of the cgo pointer passing rules\n\nCheck for invalid cgo pointer passing.\nThis looks for code that uses cgo to call C code passing values\nwhose types are almost always invalid according to the cgo pointer\nsharing rules.\nSpecifically, it warns about attempts to pass a Go chan, map, func,\nor slice to C, either directly, or via a pointer, array, or struct.",
- "Default": "true",
+ "Name": "\"QF1008\"",
+ "Doc": "Omit embedded fields from selector expression\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"composites\"",
- "Doc": "check for unkeyed composite literals\n\nThis analyzer reports a diagnostic for composite literals of struct\ntypes imported from another package that do not use the field-keyed\nsyntax. Such literals are fragile because the addition of a new field\n(even if unexported) to the struct will cause compilation to fail.\n\nAs an example,\n\n\terr = \u0026net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = \u0026net.DNSConfigError{Err: err}\n",
+ "Name": "\"QF1009\"",
+ "Doc": "Use time.Time.Equal instead of == operator\n\nAvailable since\n 2021.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"copylocks\"",
- "Doc": "check for locks erroneously passed by value\n\nInadvertently copying a value containing a lock, such as sync.Mutex or\nsync.WaitGroup, may cause both copies to malfunction. Generally such\nvalues should be referred to through a pointer.",
+ "Name": "\"QF1010\"",
+ "Doc": "Convert slice of bytes to string when printing it\n\nAvailable since\n 2021.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"deepequalerrors\"",
- "Doc": "check for calls of reflect.DeepEqual on error values\n\nThe deepequalerrors checker looks for calls of the form:\n\n reflect.DeepEqual(err1, err2)\n\nwhere err1 and err2 are errors. Using reflect.DeepEqual to compare\nerrors is discouraged.",
- "Default": "true",
+ "Name": "\"QF1011\"",
+ "Doc": "Omit redundant type from variable declaration\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"defers\"",
- "Doc": "report common mistakes in defer statements\n\nThe defers analyzer reports a diagnostic when a defer statement would\nresult in a non-deferred call to time.Since, as experience has shown\nthat this is nearly always a mistake.\n\nFor example:\n\n\tstart := time.Now()\n\t...\n\tdefer recordLatency(time.Since(start)) // error: call to time.Since is not deferred\n\nThe correct code is:\n\n\tdefer func() { recordLatency(time.Since(start)) }()",
+ "Name": "\"QF1012\"",
+ "Doc": "Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))\n\nAvailable since\n 2022.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"deprecated\"",
- "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.",
+ "Name": "\"S1000\"",
+ "Doc": "Use plain channel send or receive instead of single-case select\n\nSelect statements with a single case can be replaced with a simple\nsend or receive.\n\nBefore:\n\n select {\n case x := \u003c-ch:\n fmt.Println(x)\n }\n\nAfter:\n\n x := \u003c-ch\n fmt.Println(x)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"directive\"",
- "Doc": "check Go toolchain directives such as //go:debug\n\nThis analyzer checks for problems with known Go toolchain directives\nin all Go source files in a package directory, even those excluded by\n//go:build constraints, and all non-Go source files too.\n\nFor //go:debug (see https://go.dev/doc/godebug), the analyzer checks\nthat the directives are placed only in Go source files, only above the\npackage comment, and only in package main or *_test.go files.\n\nSupport for other known directives may be added in the future.\n\nThis analyzer does not check //go:build, which is handled by the\nbuildtag analyzer.\n",
+ "Name": "\"S1001\"",
+ "Doc": "Replace for loop with call to copy\n\nUse copy() for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.\n\nBefore:\n\n for i, x := range src {\n dst[i] = x\n }\n\nAfter:\n\n copy(dst, src)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"embed\"",
- "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.",
- "Default": "true",
+ "Name": "\"S1002\"",
+ "Doc": "Omit comparison with boolean constant\n\nBefore:\n\n if x == true {}\n\nAfter:\n\n if x {}\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"errorsas\"",
- "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+ "Name": "\"S1003\"",
+ "Doc": "Replace call to strings.Index with strings.Contains\n\nBefore:\n\n if strings.Index(x, y) != -1 {}\n\nAfter:\n\n if strings.Contains(x, y) {}\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"fillreturns\"",
- "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.",
+ "Name": "\"S1004\"",
+ "Doc": "Replace call to bytes.Compare with bytes.Equal\n\nBefore:\n\n if bytes.Compare(x, y) == 0 {}\n\nAfter:\n\n if bytes.Equal(x, y) {}\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"framepointer\"",
- "Doc": "report assembly that clobbers the frame pointer before saving it",
- "Default": "true",
+ "Name": "\"S1005\"",
+ "Doc": "Drop unnecessary use of the blank identifier\n\nIn many cases, assigning to the blank identifier is unnecessary.\n\nBefore:\n\n for _ = range s {}\n x, _ = someMap[key]\n _ = \u003c-ch\n\nAfter:\n\n for range s{}\n x = someMap[key]\n \u003c-ch\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"gofix\"",
- "Doc": "apply fixes based on go:fix comment directives\n\nThe gofix analyzer inlines functions and constants that are marked for inlining.",
- "Default": "true",
+ "Name": "\"S1006\"",
+ "Doc": "Use 'for { ... }' for infinite loops\n\nFor infinite loops, using for { ... } is the most idiomatic choice.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"hostport\"",
- "Doc": "check format of addresses passed to net.Dial\n\nThis analyzer flags code that produce network address strings using\nfmt.Sprintf, as in this example:\n\n addr := fmt.Sprintf(\"%s:%d\", host, 12345) // \"will not work with IPv6\"\n ...\n conn, err := net.Dial(\"tcp\", addr) // \"when passed to dial here\"\n\nThe analyzer suggests a fix to use the correct approach, a call to\nnet.JoinHostPort:\n\n addr := net.JoinHostPort(host, \"12345\")\n ...\n conn, err := net.Dial(\"tcp\", addr)\n\nA similar diagnostic and fix are produced for a format string of \"%s:%s\".\n",
+ "Name": "\"S1007\"",
+ "Doc": "Simplify regular expression by using raw string literal\n\nRaw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.\n\nBefore:\n\n regexp.Compile(\"\\\\A(\\\\w+) profile: total \\\\d+\\\\n\\\\z\")\n\nAfter:\n\n regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"httpresponse\"",
- "Doc": "check for mistakes using HTTP responses\n\nA common mistake when using the net/http package is to defer a function\ncall to close the http.Response Body before checking the error that\ndetermines whether the response is valid:\n\n\tresp, err := http.Head(url)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// (defer statement belongs here)\n\nThis checker helps uncover latent nil dereference bugs by reporting a\ndiagnostic for such mistakes.",
- "Default": "true",
+ "Name": "\"S1008\"",
+ "Doc": "Simplify returning boolean expression\n\nBefore:\n\n if \u003cexpr\u003e {\n return true\n }\n return false\n\nAfter:\n\n return \u003cexpr\u003e\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"ifaceassert\"",
- "Doc": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.",
+ "Name": "\"S1009\"",
+ "Doc": "Omit redundant nil check on slices, maps, and channels\n\nThe len function is defined for all slices, maps, and\nchannels, even nil ones, which have a length of zero. It is not necessary to\ncheck for nil before checking that their length is not zero.\n\nBefore:\n\n if x != nil \u0026\u0026 len(x) != 0 {}\n\nAfter:\n\n if len(x) != 0 {}\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"infertypeargs\"",
- "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n",
+ "Name": "\"S1010\"",
+ "Doc": "Omit default slice index\n\nWhen slicing, the second index defaults to the length of the value,\nmaking s[n:len(s)] and s[n:] equivalent.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"loopclosure\"",
- "Doc": "check references to loop variables from within nested functions\n\nThis analyzer reports places where a function literal references the\niteration variable of an enclosing loop, and the loop calls the function\nin such a way (e.g. with go or defer) that it may outlive the loop\niteration and possibly observe the wrong value of the variable.\n\nNote: An iteration variable can only outlive a loop iteration in Go versions \u003c=1.21.\nIn Go 1.22 and later, the loop variable lifetimes changed to create a new\niteration variable per loop iteration. (See go.dev/issue/60078.)\n\nIn this example, all the deferred functions run after the loop has\ncompleted, so all observe the final value of v [\u003cgo1.22].\n\n\tfor _, v := range list {\n\t defer func() {\n\t use(v) // incorrect\n\t }()\n\t}\n\nOne fix is to create a new variable for each iteration of the loop:\n\n\tfor _, v := range list {\n\t v := v // new var per iteration\n\t defer func() {\n\t use(v) // ok\n\t }()\n\t}\n\nAfter Go version 1.22, the previous two for loops are equivalent\nand both are correct.\n\nThe next example uses a go statement and has a similar problem [\u003cgo1.22].\nIn addition, it has a data race because the loop updates v\nconcurrent with the goroutines accessing it.\n\n\tfor _, v := range elem {\n\t go func() {\n\t use(v) // incorrect, and a data race\n\t }()\n\t}\n\nA fix is the same as before. The checker also reports problems\nin goroutines started by golang.org/x/sync/errgroup.Group.\nA hard-to-spot variant of this form is common in parallel tests:\n\n\tfunc Test(t *testing.T) {\n\t for _, test := range tests {\n\t t.Run(test.name, func(t *testing.T) {\n\t t.Parallel()\n\t use(test) // incorrect, and a data race\n\t })\n\t }\n\t}\n\nThe t.Parallel() call causes the rest of the function to execute\nconcurrent with the loop [\u003cgo1.22].\n\nThe analyzer reports references only in the last statement,\nas it is not deep enough to understand the effects of subsequent\nstatements that might render the reference benign.\n(\"Last statement\" is defined recursively in compound\nstatements such as if, switch, and select.)\n\nSee: https://golang.org/doc/go_faq.html#closures_and_goroutines",
- "Default": "true",
+ "Name": "\"S1011\"",
+ "Doc": "Use a single append to concatenate two slices\n\nBefore:\n\n for _, e := range y {\n x = append(x, e)\n }\n \n for i := range y {\n x = append(x, y[i])\n }\n \n for i := range y {\n v := y[i]\n x = append(x, v)\n }\n\nAfter:\n\n x = append(x, y...)\n x = append(x, y...)\n x = append(x, y...)\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"lostcancel\"",
- "Doc": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nWithDeadline and variants such as WithCancelCause must be called,\nor the new context will remain live until its parent context is cancelled.\n(The background context is never cancelled.)",
+ "Name": "\"S1012\"",
+ "Doc": "Replace time.Now().Sub(x) with time.Since(x)\n\nThe time.Since helper has the same effect as using time.Now().Sub(x)\nbut is easier to read.\n\nBefore:\n\n time.Now().Sub(x)\n\nAfter:\n\n time.Since(x)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"modernize\"",
- "Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go and its standard\nlibrary.\n\nEach diagnostic provides a fix. Our intent is that these fixes may\nbe safely applied en masse without changing the behavior of your\nprogram. In some cases the suggested fixes are imperfect and may\nlead to (for example) unused imports or unused local variables,\ncausing build breakage. However, these problems are generally\ntrivial to fix. We regard any modernizer whose fix changes program\nbehavior to have a serious bug and will endeavor to fix it.\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.\n\nChanges produced by this tool should be reviewed as usual before\nbeing merged. In some cases, a loop may be replaced by a simple\nfunction call, causing comments within the loop to be discarded.\nHuman judgment may be required to avoid losing comments of value.\n\nEach diagnostic reported by modernize has a specific category. (The\ncategories are listed below.) Diagnostics in some categories, such\nas \"efaceany\" (which replaces \"interface{}\" with \"any\" where it is\nsafe to do so) are particularly numerous. It may ease the burden of\ncode review to apply fixes in two passes, the first change\nconsisting only of fixes of category \"efaceany\", the second\nconsisting of all others. This can be achieved using the -category flag:\n\n\t$ modernize -category=efaceany -fix -test ./...\n\t$ modernize -category=-efaceany -fix -test ./...\n\nCategories of modernize diagnostic:\n\n - forvar: remove x := x variable declarations made unnecessary by the new semantics of loops in go1.22.\n\n - slicescontains: replace 'for i, elem := range s { if elem == needle { ...; break }'\n by a call to slices.Contains, added in go1.21.\n\n - minmax: replace an if/else conditional assignment by a call to\n the built-in min or max functions added in go1.21.\n\n - sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21.\n\n - efaceany: replace interface{} by the 'any' type added in go1.18.\n\n - slicesclone: replace append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21.\n\n - mapsloop: replace a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions from\n the maps package, added in go1.21.\n\n - fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19.\n\n - testingcontext: replace uses of context.WithCancel in tests\n with t.Context, added in go1.24.\n\n - omitzero: replace omitempty by omitzero on structs, added in go1.24.\n\n - bloop: replace \"for i := range b.N\" or \"for range b.N\" in a\n benchmark with \"for b.Loop()\", and remove any preceding calls\n to b.StopTimer, b.StartTimer, and b.ResetTimer.\n\n - slicesdelete: replace append(s[:i], s[i+1]...) by\n slices.Delete(s, i, i+1), added in go1.21.\n\n - rangeint: replace a 3-clause \"for i := 0; i \u003c n; i++\" loop by\n \"for i := range n\", added in go1.22.\n\n - stringsseq: replace Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq.\n\n - stringscutprefix: replace some uses of HasPrefix followed by TrimPrefix with CutPrefix,\n added to the strings package in go1.20.\n\n - waitgroup: replace old complex usages of sync.WaitGroup by less complex WaitGroup.Go method in go1.25.",
+ "Name": "\"S1016\"",
+ "Doc": "Use a type conversion instead of manually copying struct fields\n\nTwo struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.\n\nBefore:\n\n var x T1\n y := T2{\n Field1: x.Field1,\n Field2: x.Field2,\n }\n\nAfter:\n\n var x T1\n y := T2(x)\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"S1017\"",
+ "Doc": "Replace manual trimming with strings.TrimPrefix\n\nInstead of using strings.HasPrefix and manual slicing, use the\nstrings.TrimPrefix function. If the string doesn't start with the\nprefix, the original string will be returned. Using strings.TrimPrefix\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.\n\nBefore:\n\n if strings.HasPrefix(str, prefix) {\n str = str[len(prefix):]\n }\n\nAfter:\n\n str = strings.TrimPrefix(str, prefix)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"nilfunc\"",
- "Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.",
+ "Name": "\"S1018\"",
+ "Doc": "Use 'copy' for sliding elements\n\ncopy() permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.\n\nBefore:\n\n for i := 0; i \u003c n; i++ {\n bs[i] = bs[offset+i]\n }\n\nAfter:\n\n copy(bs[:n], bs[offset:])\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"nilness\"",
- "Doc": "check for redundant or impossible nil comparisons\n\nThe nilness checker inspects the control-flow graph of each function in\na package and reports nil pointer dereferences, degenerate nil\npointers, and panics with nil values. A degenerate comparison is of the form\nx==nil or x!=nil where x is statically known to be nil or non-nil. These are\noften a mistake, especially in control flow related to errors. Panics with nil\nvalues are checked because they are not detectable by\n\n\tif r := recover(); r != nil {\n\nThis check reports conditions such as:\n\n\tif f == nil { // impossible condition (f is a function)\n\t}\n\nand:\n\n\tp := \u0026v\n\t...\n\tif p != nil { // tautological condition\n\t}\n\nand:\n\n\tif p == nil {\n\t\tprint(*p) // nil dereference\n\t}\n\nand:\n\n\tif p == nil {\n\t\tpanic(p)\n\t}\n\nSometimes the control flow may be quite complex, making bugs hard\nto spot. In the example below, the err.Error expression is\nguaranteed to panic because, after the first return, err must be\nnil. The intervening loop is just a distraction.\n\n\t...\n\terr := g.Wait()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpartialSuccess := false\n\tfor _, err := range errs {\n\t\tif err == nil {\n\t\t\tpartialSuccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif partialSuccess {\n\t\treportStatus(StatusMessage{\n\t\t\tCode: code.ERROR,\n\t\t\tDetail: err.Error(), // \"nil dereference in dynamic method call\"\n\t\t})\n\t\treturn nil\n\t}\n\n...",
+ "Name": "\"S1019\"",
+ "Doc": "Simplify 'make' call by omitting redundant arguments\n\nThe 'make' function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"nonewvars\"",
- "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2",
+ "Name": "\"S1020\"",
+ "Doc": "Omit redundant nil check in type assertion\n\nBefore:\n\n if _, ok := i.(T); ok \u0026\u0026 i != nil {}\n\nAfter:\n\n if _, ok := i.(T); ok {}\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"noresultvalues\"",
- "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }",
+ "Name": "\"S1021\"",
+ "Doc": "Merge variable declaration and assignment\n\nBefore:\n\n var x uint\n x = 1\n\nAfter:\n\n var x uint = 1\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"S1023\"",
+ "Doc": "Omit redundant control flow\n\nFunctions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"printf\"",
- "Doc": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.",
+ "Name": "\"S1024\"",
+ "Doc": "Replace x.Sub(time.Now()) with time.Until(x)\n\nThe time.Until helper has the same effect as using x.Sub(time.Now())\nbut is easier to read.\n\nBefore:\n\n x.Sub(time.Now())\n\nAfter:\n\n time.Until(x)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"shadow\"",
- "Doc": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}",
+ "Name": "\"S1025\"",
+ "Doc": "Don't use fmt.Sprintf(\"%s\", x) unnecessarily\n\nIn many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n type T1 string\n type T2 int\n\n func (T2) String() string { return \"Hello, world\" }\n\n var x string\n var y T1\n var z T2\n\nwe can simplify\n\n fmt.Sprintf(\"%s\", x)\n fmt.Sprintf(\"%s\", y)\n fmt.Sprintf(\"%s\", z)\n\nto\n\n x\n string(y)\n z.String()\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"shift\"",
- "Doc": "check for shifts that equal or exceed the width of the integer",
+ "Name": "\"S1028\"",
+ "Doc": "Simplify error construction with fmt.Errorf\n\nBefore:\n\n errors.New(fmt.Sprintf(...))\n\nAfter:\n\n fmt.Errorf(...)\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"sigchanyzer\"",
- "Doc": "check for unbuffered channel of os.Signal\n\nThis checker reports call expression of the form\n\n\tsignal.Notify(c \u003c-chan os.Signal, sig ...os.Signal),\n\nwhere c is an unbuffered channel, which can be at risk of missing the signal.",
- "Default": "true",
+ "Name": "\"S1029\"",
+ "Doc": "Range over the string directly\n\nRanging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.\n\nBefore:\n\n for _, r := range []rune(s) {}\n\nAfter:\n\n for _, r := range s {}\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"simplifycompositelit\"",
- "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+ "Name": "\"S1030\"",
+ "Doc": "Use bytes.Buffer.String or bytes.Buffer.Bytes\n\nbytes.Buffer has both a String and a Bytes method. It is almost never\nnecessary to use string(buf.Bytes()) or []byte(buf.String()) – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\nm[string(buf.Bytes())] is more efficient than m[buf.String()].\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"simplifyrange\"",
- "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+ "Name": "\"S1031\"",
+ "Doc": "Omit redundant nil check around loop\n\nYou can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.\n\nBefore:\n\n if s != nil {\n for _, x := range s {\n ...\n }\n }\n\nAfter:\n\n for _, x := range s {\n ...\n }\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"simplifyslice\"",
- "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+ "Name": "\"S1032\"",
+ "Doc": "Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)\n\nThe sort.Ints, sort.Float64s and sort.Strings functions are easier to\nread than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))\nand sort.Sort(sort.StringSlice(x)).\n\nBefore:\n\n sort.Sort(sort.StringSlice(x))\n\nAfter:\n\n sort.Strings(x)\n\nAvailable since\n 2019.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"slog\"",
- "Doc": "check for invalid structured logging calls\n\nThe slog checker looks for calls to functions from the log/slog\npackage that take alternating key-value pairs. It reports calls\nwhere an argument in a key position is neither a string nor a\nslog.Attr, and where a final key is missing its value.\nFor example,it would report\n\n\tslog.Warn(\"message\", 11, \"k\") // slog.Warn arg \"11\" should be a string or a slog.Attr\n\nand\n\n\tslog.Info(\"message\", \"k1\", v1, \"k2\") // call to slog.Info missing a final value",
+ "Name": "\"S1033\"",
+ "Doc": "Unnecessary guard around call to 'delete'\n\nCalling delete on a nil map is a no-op.\n\nAvailable since\n 2019.2\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"sortslice\"",
- "Doc": "check the argument type of sort.Slice\n\nsort.Slice requires an argument of a slice type. Check that\nthe interface{} value passed to sort.Slice is actually a slice.",
+ "Name": "\"S1034\"",
+ "Doc": "Use result of type assertion to simplify cases\n\nAvailable since\n 2019.2\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"stdmethods\"",
- "Doc": "check signature of methods of well-known interfaces\n\nSometimes a type may be intended to satisfy an interface but may fail to\ndo so because of a mistake in its method signature.\nFor example, the result of this WriteTo method should be (int64, error),\nnot error, to satisfy io.WriterTo:\n\n\ttype myWriterTo struct{...}\n\tfunc (myWriterTo) WriteTo(w io.Writer) error { ... }\n\nThis check ensures that each method whose name matches one of several\nwell-known interface methods from the standard library has the correct\nsignature for that interface.\n\nChecked method names include:\n\n\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo",
+ "Name": "\"S1035\"",
+ "Doc": "Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header\n\nThe methods on net/http.Header, namely Add, Del, Get\nand Set, already canonicalize the given header name.\n\nAvailable since\n 2020.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"stdversion\"",
- "Doc": "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n",
+ "Name": "\"S1036\"",
+ "Doc": "Unnecessary guard around map access\n\nWhen accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n if _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n } else {\n m[\"foo\"] = []string{\"bar\"}\n }\n\ncan be simplified to\n\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n\nand\n\n if _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n } else {\n m2[\"k\"] = 4\n }\n\ncan be simplified to\n\n m[\"k\"] += 4\n\nAvailable since\n 2020.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"stringintconv\"",
- "Doc": "check for string(int) conversions\n\nThis checker flags conversions of the form string(x) where x is an integer\n(but not byte or rune) type. Such conversions are discouraged because they\nreturn the UTF-8 representation of the Unicode code point x, and not a decimal\nstring representation of x as one might expect. Furthermore, if x denotes an\ninvalid code point, the conversion cannot be statically rejected.\n\nFor conversions that intend on using the code point, consider replacing them\nwith string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the\nstring representation of the value in the desired base.",
+ "Name": "\"S1037\"",
+ "Doc": "Elaborate way of sleeping\n\nUsing a select statement with a single case receiving\nfrom the result of time.After is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.\n\nAvailable since\n 2020.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"structtag\"",
- "Doc": "check that struct field tags conform to reflect.StructTag.Get\n\nAlso report certain struct tags (json, xml) used with unexported fields.",
+ "Name": "\"S1038\"",
+ "Doc": "Unnecessarily complex way of printing formatted string\n\nInstead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).\n\nAvailable since\n 2020.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"testinggoroutine\"",
- "Doc": "report calls to (*testing.T).Fatal from goroutines started by a test\n\nFunctions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and\nSkip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.\nThis checker detects calls to these functions that occur within a goroutine\nstarted by the test. For example:\n\n\tfunc TestFoo(t *testing.T) {\n\t go func() {\n\t t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n\t }()\n\t}",
+ "Name": "\"S1039\"",
+ "Doc": "Unnecessary use of fmt.Sprint\n\nCalling fmt.Sprint with a single string argument is unnecessary\nand identical to using the string directly.\n\nAvailable since\n 2020.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"tests\"",
- "Doc": "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark, Fuzzing and Example functions checking\nmalformed names, wrong signatures and examples documenting non-existent\nidentifiers.\n\nPlease see the documentation for package testing in golang.org/pkg/testing\nfor the conventions that are enforced for Tests, Benchmarks, and Examples.",
+ "Name": "\"S1040\"",
+ "Doc": "Type assertion to current type\n\nThe type assertion x.(SomeInterface), when x already has type\nSomeInterface, can only fail if x is nil. Usually, this is\nleft-over code from when x had a different type and you can safely\ndelete the type assertion. If you want to check that x is not nil,\nconsider being explicit and using an actual if x == nil comparison\ninstead of relying on the type assertion panicking.\n\nAvailable since\n 2021.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"timeformat\"",
- "Doc": "check for calls of (time.Time).Format or time.Parse with 2006-02-01\n\nThe timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)\nformat. Internationally, \"yyyy-dd-mm\" does not occur in common calendar date\nstandards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.",
- "Default": "true",
+ "Name": "\"SA1000\"",
+ "Doc": "Invalid regular expression\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"unmarshal\"",
- "Doc": "report passing non-pointer or non-interface values to unmarshal\n\nThe unmarshal analysis reports calls to functions such as json.Unmarshal\nin which the argument type is not a pointer or an interface.",
+ "Name": "\"SA1001\"",
+ "Doc": "Invalid template\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"unreachable\"",
- "Doc": "check for unreachable code\n\nThe unreachable analyzer finds statements that execution can never reach\nbecause they are preceded by a return statement, a call to panic, an\ninfinite loop, or similar constructs.",
- "Default": "true",
+ "Name": "\"SA1002\"",
+ "Doc": "Invalid format in time.Parse\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"unsafeptr\"",
- "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.",
- "Default": "true",
+ "Name": "\"SA1003\"",
+ "Doc": "Unsupported argument to functions in encoding/binary\n\nThe encoding/binary package can only serialize types with known sizes.\nThis precludes the use of the int and uint types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, bool wasn't supported, either.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"unusedfunc\"",
- "Doc": "check for unused functions and methods\n\nThe unusedfunc analyzer reports functions and methods that are\nnever referenced outside of their own declaration.\n\nA function is considered unused if it is unexported and not\nreferenced (except within its own declaration).\n\nA method is considered unused if it is unexported, not referenced\n(except within its own declaration), and its name does not match\nthat of any method of an interface type declared within the same\npackage.\n\nThe tool may report false positives in some situations, for\nexample:\n\n - For a declaration of an unexported function that is referenced\n from another package using the go:linkname mechanism, if the\n declaration's doc comment does not also have a go:linkname\n comment.\n\n (Such code is in any case strongly discouraged: linkname\n annotations, if they must be used at all, should be used on both\n the declaration and the alias.)\n\n - For compiler intrinsics in the \"runtime\" package that, though\n never referenced, are known to the compiler and are called\n indirectly by compiled object code.\n\n - For functions called only from assembly.\n\n - For functions called only from files whose build tags are not\n selected in the current build configuration.\n\nSee https://github.com/golang/go/issues/71686 for discussion of\nthese limitations.\n\nThe unusedfunc algorithm is not as precise as the\ngolang.org/x/tools/cmd/deadcode tool, but it has the advantage that\nit runs within the modular analysis framework, enabling near\nreal-time feedback within gopls.",
+ "Name": "\"SA1004\"",
+ "Doc": "Suspiciously small untyped constant in time.Sleep\n\nThe time.Sleep function takes a time.Duration as its only argument.\nDurations are expressed in nanoseconds. Thus, calling time.Sleep(1)\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe time package provides constants such as time.Second to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example 5 * time.Second for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\nn * time.Nanosecond to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"unusedparams\"",
- "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.\n\nThis analyzer ignores generated code.",
+ "Name": "\"SA1005\"",
+ "Doc": "Invalid first argument to exec.Command\n\nos/exec runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n ls / /tmp\n\nwould be\n\n exec.Command(\"ls\", \"/\", \"/tmp\")\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a /bin/sh program:\n\n exec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"unusedresult\"",
- "Doc": "check for unused results of calls to some functions\n\nSome functions like fmt.Errorf return a result and have no side\neffects, so it is always a mistake to discard the result. Other\nfunctions may return an error that must not be ignored, or a cleanup\noperation that must be called. This analyzer reports calls to\nfunctions like these when the result of the call is ignored.\n\nThe set of functions may be controlled using flags.",
- "Default": "true",
+ "Name": "\"SA1007\"",
+ "Doc": "Invalid URL in net/url.Parse\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"unusedvariable\"",
- "Doc": "check for unused variables and suggest fixes",
+ "Name": "\"SA1008\"",
+ "Doc": "Non-canonical key in http.Header map\n\nKeys in http.Header maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\nhttp.Header.Add and http.Header.Del convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating http.Header maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n h := http.Header{}\n h[\"etag\"] = []string{\"1234\"}\n h.Add(\"etag\", \"5678\")\n fmt.Println(h)\n\n // Output:\n // map[Etag:[5678] etag:[1234]]\n\nThe easiest way of obtaining the canonical form of a key is to use\nhttp.CanonicalHeaderKey.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"unusedwrite\"",
- "Doc": "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}",
- "Default": "true",
+ "Name": "\"SA1010\"",
+ "Doc": "(*regexp.Regexp).FindAll called with n == 0, which will always return zero results\n\nIf n \u003e= 0, the function returns at most n matches/submatches. To\nreturn all results, specify a negative number.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"waitgroup\"",
- "Doc": "check for misuses of sync.WaitGroup\n\nThis analyzer detects mistaken calls to the (*sync.WaitGroup).Add\nmethod from inside a new goroutine, causing Add to race with Wait:\n\n\t// WRONG\n\tvar wg sync.WaitGroup\n\tgo func() {\n\t wg.Add(1) // \"WaitGroup.Add called from inside new goroutine\"\n\t defer wg.Done()\n\t ...\n\t}()\n\twg.Wait() // (may return prematurely before new goroutine starts)\n\nThe correct code calls Add before starting the goroutine:\n\n\t// RIGHT\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\twg.Wait()",
- "Default": "true",
+ "Name": "\"SA1011\"",
+ "Doc": "Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"yield\"",
- "Doc": "report calls to yield where the result is ignored\n\nAfter a yield function returns false, the caller should not call\nthe yield function again; generally the iterator should return\npromptly.\n\nThis example fails to check the result of the call to yield,\ncausing this analyzer to report a diagnostic:\n\n\tyield(1) // yield may be called again (on L2) after returning false\n\tyield(2)\n\nThe corrected code is either this:\n\n\tif yield(1) { yield(2) }\n\nor simply:\n\n\t_ = yield(1) \u0026\u0026 yield(2)\n\nIt is not always a mistake to ignore the result of yield.\nFor example, this is a valid single-element iterator:\n\n\tyield(1) // ok to ignore result\n\treturn\n\nIt is only a mistake when the yield call that returned false may be\nfollowed by another call.",
+ "Name": "\"SA1012\"",
+ "Doc": "A nil context.Context is being passed to a function, consider using context.TODO instead\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
- }
- ]
- },
- "EnumValues": null,
- "Default": "{}",
- "Status": "",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "staticcheck",
- "Type": "bool",
- "Doc": "staticcheck enables additional analyses from staticcheck.io.\nThese analyses are documented on\n[Staticcheck's website](https://staticcheck.io/docs/checks/).\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "false",
- "Status": "experimental",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "annotations",
- "Type": "map[enum]bool",
- "Doc": "annotations specifies the various kinds of compiler\noptimization details that should be reported as diagnostics\nwhen enabled for a package by the \"Toggle compiler\noptimization details\" (`gopls.gc_details`) command.\n\n(Some users care only about one kind of annotation in their\nprofiling efforts. More importantly, in large packages, the\nnumber of annotations can sometimes overwhelm the user\ninterface and exceed the per-file diagnostic limit.)\n\nTODO(adonovan): rename this field to CompilerOptDetail.\n",
- "EnumKeys": {
- "ValueType": "bool",
- "Keys": [
+ },
{
- "Name": "\"bounds\"",
- "Doc": "`\"bounds\"` controls bounds checking diagnostics.\n",
+ "Name": "\"SA1013\"",
+ "Doc": "io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"escape\"",
- "Doc": "`\"escape\"` controls diagnostics about escape choices.\n",
- "Default": "true",
+ "Name": "\"SA1014\"",
+ "Doc": "Non-pointer value passed to Unmarshal or Decode\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
"Status": ""
},
{
- "Name": "\"inline\"",
- "Doc": "`\"inline\"` controls diagnostics about inlining choices.\n",
+ "Name": "\"SA1015\"",
+ "Doc": "Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions\n\nBefore Go 1.23, time.Tickers had to be closed to be able to be garbage\ncollected. Since time.Tick doesn't make it possible to close the underlying\nticker, using it repeatedly would leak memory.\n\nGo 1.23 fixes this by allowing tickers to be collected even if they weren't closed.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1016\"",
+ "Doc": "Trapping a signal that cannot be trapped\n\nNot all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"nil\"",
- "Doc": "`\"nil\"` controls nil checks.\n",
+ "Name": "\"SA1017\"",
+ "Doc": "Channels used with os/signal.Notify should be buffered\n\nThe os/signal package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1018\"",
+ "Doc": "strings.Replace called with n == 0, which does nothing\n\nWith n == 0, zero instances will be replaced. To replace all\ninstances, use a negative number, or use strings.ReplaceAll.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1020\"",
+ "Doc": "Using an invalid host:port pair with a net.Listen-related function\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1021\"",
+ "Doc": "Using bytes.Equal to compare two net.IP\n\nA net.IP stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two net.IPs, the net.IP.Equal method should\nbe used, as it takes both representations into account.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1023\"",
+ "Doc": "Modifying the buffer in an io.Writer implementation\n\nWrite must not modify the slice data, even temporarily.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1024\"",
+ "Doc": "A string cutset contains duplicate characters\n\nThe strings.TrimLeft and strings.TrimRight functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n strings.TrimLeft(\"42133word\", \"1234\")\n\nwill result in the string \"word\" – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use strings.TrimPrefix instead.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1025\"",
+ "Doc": "It is not possible to use (*time.Timer).Reset's return value correctly\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1026\"",
+ "Doc": "Cannot marshal channels or functions\n\nAvailable since\n 2019.2\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1027\"",
+ "Doc": "Atomic access to 64-bit variable must be 64-bit aligned\n\nOn ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.\n\nAvailable since\n 2019.2\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1028\"",
+ "Doc": "sort.Slice can only be used on slices\n\nThe first argument of sort.Slice must be a slice.\n\nAvailable since\n 2020.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1029\"",
+ "Doc": "Inappropriate key in call to context.WithValue\n\nThe provided key must be comparable and should not be\nof type string or any other built-in type to avoid collisions between\npackages using context. Users of WithValue should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an interface{},\ncontext keys often have concrete type struct{}. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.\n\nAvailable since\n 2020.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1030\"",
+ "Doc": "Invalid argument in call to a strconv function\n\nThis check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in strconv.\n\nAvailable since\n 2021.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1031\"",
+ "Doc": "Overlapping byte slices passed to an encoder\n\nIn an encoding function of the form Encode(dst, src), dst and\nsrc were found to reference the same memory. This can result in\nsrc bytes being overwritten before they are read, when the encoder\nwrites more than one byte per src byte.\n\nAvailable since\n 2024.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA1032\"",
+ "Doc": "Wrong order of arguments to errors.Is\n\nThe first argument of the function errors.Is is the error\nthat we have and the second argument is the error we're trying to match against.\nFor example:\n\n\tif errors.Is(err, io.EOF) { ... }\n\nThis check detects some cases where the two arguments have been swapped. It\nflags any calls where the first argument is referring to a package-level error\nvariable, such as\n\n\tif errors.Is(io.EOF, err) { /* this is wrong */ }\n\nAvailable since\n 2024.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA2001\"",
+ "Doc": "Empty critical section, did you mean to defer the unlock?\n\nEmpty critical sections of the kind\n\n mu.Lock()\n mu.Unlock()\n\nare very often a typo, and the following was intended instead:\n\n mu.Lock()\n defer mu.Unlock()\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a //lint:ignore directive can be used to suppress this\nrare false positive.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
- }
- ]
- },
- "EnumValues": null,
- "Default": "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}",
- "Status": "",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "vulncheck",
- "Type": "enum",
- "Doc": "vulncheck enables vulnerability scanning.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": [
- {
- "Value": "\"Imports\"",
- "Doc": "`\"Imports\"`: In Imports mode, `gopls` will report vulnerabilities that affect packages\ndirectly and indirectly used by the analyzed main module.\n",
- "Status": ""
- },
- {
- "Value": "\"Off\"",
- "Doc": "`\"Off\"`: Disable vulnerability analysis.\n",
- "Status": ""
- }
- ],
- "Default": "\"Off\"",
- "Status": "experimental",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "diagnosticsDelay",
- "Type": "time.Duration",
- "Doc": "diagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "\"1s\"",
- "Status": "advanced",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "diagnosticsTrigger",
- "Type": "enum",
- "Doc": "diagnosticsTrigger controls when to run diagnostics.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": [
- {
- "Value": "\"Edit\"",
- "Doc": "`\"Edit\"`: Trigger diagnostics on file edit and save. (default)\n",
- "Status": ""
- },
- {
- "Value": "\"Save\"",
- "Doc": "`\"Save\"`: Trigger diagnostics only on file save. Events like initial workspace load\nor configuration change will still trigger diagnostics.\n",
- "Status": ""
- }
- ],
- "Default": "\"Edit\"",
- "Status": "experimental",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "analysisProgressReporting",
- "Type": "bool",
- "Doc": "analysisProgressReporting controls whether gopls sends progress\nnotifications when construction of its index of analysis facts is taking a\nlong time. Cancelling these notifications will cancel the indexing task,\nthough it will restart after the next change in the workspace.\n\nWhen a package is opened for the first time and heavyweight analyses such as\nstaticcheck are enabled, it can take a while to construct the index of\nanalysis facts for all its dependencies. The index is cached in the\nfilesystem, so subsequent analysis should be faster.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "true",
- "Status": "",
- "Hierarchy": "ui.diagnostic",
- "DeprecationMessage": ""
- },
- {
- "Name": "hints",
- "Type": "map[enum]bool",
- "Doc": "hints specify inlay hints that users want to see. A full list of hints\nthat gopls uses can be found in\n[inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).\n",
- "EnumKeys": {
- "ValueType": "bool",
- "Keys": [
+ },
{
- "Name": "\"assignVariableTypes\"",
- "Doc": "`\"assignVariableTypes\"` controls inlay hints for variable types in assign statements:\n```go\n\ti/* int*/, j/* int*/ := 0, len(r)-1\n```\n",
+ "Name": "\"SA2002\"",
+ "Doc": "Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"compositeLiteralFields\"",
- "Doc": "`\"compositeLiteralFields\"` inlay hints for composite literal field names:\n```go\n\t{/*in: */\"Hello, world\", /*want: */\"dlrow ,olleH\"}\n```\n",
+ "Name": "\"SA2003\"",
+ "Doc": "Deferred Lock right after locking, likely meant to defer Unlock instead\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"compositeLiteralTypes\"",
- "Doc": "`\"compositeLiteralTypes\"` controls inlay hints for composite literal types:\n```go\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}\n```\n",
+ "Name": "\"SA3000\"",
+ "Doc": "TestMain doesn't call os.Exit, hiding test failures\n\nTest executables (and in turn 'go test') exit with a non-zero status\ncode if any tests failed. When specifying your own TestMain function,\nit is your responsibility to arrange for this, by calling os.Exit with\nthe correct code. The correct code is returned by (*testing.M).Run, so\nthe usual way of implementing TestMain is to end it with\nos.Exit(m.Run()).\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA3001\"",
+ "Doc": "Assigning to b.N in benchmarks distorts the results\n\nThe testing package dynamically sets b.N to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter b.N as this would\nfalsify results.\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4000\"",
+ "Doc": "Binary operator has identical expressions on both sides\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4001\"",
+ "Doc": "\u0026*x gets simplified to x, it does not copy x\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4003\"",
+ "Doc": "Comparing unsigned values against negative values is pointless\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4004\"",
+ "Doc": "The loop exits unconditionally after one iteration\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4005\"",
+ "Doc": "Field assignment that will never be observed. Did you mean to use a pointer receiver?\n\nAvailable since\n 2021.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"constantValues\"",
- "Doc": "`\"constantValues\"` controls inlay hints for constant values:\n```go\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)\n```\n",
+ "Name": "\"SA4006\"",
+ "Doc": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"functionTypeParameters\"",
- "Doc": "`\"functionTypeParameters\"` inlay hints for implicit type parameters on generic functions:\n```go\n\tmyFoo/*[int, string]*/(1, \"hello\")\n```\n",
+ "Name": "\"SA4008\"",
+ "Doc": "The variable in the loop condition never changes, are you incrementing the wrong variable?\n\nFor example:\n\n\tfor i := 0; i \u003c 10; j++ { ... }\n\nThis may also occur when a loop can only execute once because of unconditional\ncontrol flow that terminates the loop. For example, when a loop body contains an\nunconditional break, return, or panic:\n\n\tfunc f() {\n\t\tpanic(\"oops\")\n\t}\n\tfunc g() {\n\t\tfor i := 0; i \u003c 10; i++ {\n\t\t\t// f unconditionally calls panic, which means \"i\" is\n\t\t\t// never incremented.\n\t\t\tf()\n\t\t}\n\t}\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"parameterNames\"",
- "Doc": "`\"parameterNames\"` controls inlay hints for parameter names:\n```go\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)\n```\n",
+ "Name": "\"SA4009\"",
+ "Doc": "A function argument is overwritten before its first use\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"rangeVariableTypes\"",
- "Doc": "`\"rangeVariableTypes\"` controls inlay hints for variable types in range statements:\n```go\n\tfor k/* int*/, v/* string*/ := range []string{} {\n\t\tfmt.Println(k, v)\n\t}\n```\n",
+ "Name": "\"SA4010\"",
+ "Doc": "The result of append will never be observed anywhere\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
- }
- ]
- },
- "EnumValues": null,
- "Default": "{}",
- "Status": "experimental",
- "Hierarchy": "ui.inlayhint",
- "DeprecationMessage": ""
- },
- {
- "Name": "codelenses",
- "Type": "map[enum]bool",
- "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n }\n...\n}\n```\n",
- "EnumKeys": {
- "ValueType": "bool",
- "Keys": [
+ },
{
- "Name": "\"generate\"",
- "Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
+ "Name": "\"SA4011\"",
+ "Doc": "Break statement with no effect. Did you mean to break out of an outer loop?\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"regenerate_cgo\"",
- "Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
+ "Name": "\"SA4012\"",
+ "Doc": "Comparing a value against NaN even though no value is equal to NaN\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4013\"",
+ "Doc": "Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"run_govulncheck\"",
- "Doc": "`\"run_govulncheck\"`: Run govulncheck (legacy)\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+ "Name": "\"SA4014\"",
+ "Doc": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4015\"",
+ "Doc": "Calling functions like math.Ceil on floats converted from integers doesn't do anything useful\n\nAvailable since\n 2017.1\n",
"Default": "false",
- "Status": "experimental"
+ "Status": ""
},
{
- "Name": "\"test\"",
- "Doc": "`\"test\"`: Run tests and benchmarks\n\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
+ "Name": "\"SA4016\"",
+ "Doc": "Certain bitwise operations, such as x ^ 0, do not do anything useful\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4017\"",
+ "Doc": "Discarding the return values of a function without side effects, making the call pointless\n\nAvailable since\n 2017.1\n",
"Default": "false",
"Status": ""
},
{
- "Name": "\"tidy\"",
- "Doc": "`\"tidy\"`: Tidy go.mod file\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
+ "Name": "\"SA4018\"",
+ "Doc": "Self-assignment of variables\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4019\"",
+ "Doc": "Multiple, identical build constraints in the same file\n\nAvailable since\n 2017.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"upgrade_dependency\"",
- "Doc": "`\"upgrade_dependency\"`: Update dependencies\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
+ "Name": "\"SA4020\"",
+ "Doc": "Unreachable case clause in a type switch\n\nIn a type switch like the following\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n\n var v interface{} = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case T:\n // unreachable\n }\n\nthe second case clause can never be reached because T implements\nio.Reader and case clauses are evaluated in source order.\n\nAnother example:\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n func (T) Close() error { return nil }\n\n var v interface{} = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case io.ReadCloser:\n // unreachable\n }\n\nEven though T has a Close method and thus implements io.ReadCloser,\nio.Reader will always match first. The method set of io.Reader is a\nsubset of io.ReadCloser. Thus it is impossible to match the second\ncase without matching the first case.\n\n\nStructurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n type T error\n type V error\n\n func doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n }\n\nthe following type switch will have an unreachable case clause:\n\n switch doSomething().(type) {\n case T:\n // ...\n case V:\n // unreachable\n }\n\nT will always match before V because they are structurally equivalent\nand therefore doSomething()'s return value implements both.\n\nAvailable since\n 2019.2\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"vendor\"",
- "Doc": "`\"vendor\"`: Update vendor directory\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
+ "Name": "\"SA4022\"",
+ "Doc": "Comparing the address of a variable against nil\n\nCode such as 'if \u0026x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.\n\nAvailable since\n 2020.1\n",
"Default": "true",
"Status": ""
},
{
- "Name": "\"vulncheck\"",
- "Doc": "`\"vulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+ "Name": "\"SA4023\"",
+ "Doc": "Impossible comparison of interface value with untyped nil\n\nUnder the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n func returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n }\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n func returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n }\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as *MyError, to help guarantee the error is\ncreated correctly. As an example, os.Open returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection at https://golang.org/doc/articles/laws_of_reflection.html.\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.\n\nAvailable since\n 2020.2\n",
"Default": "false",
- "Status": "experimental"
- }
- ]
- },
- "EnumValues": null,
- "Default": "{\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":false,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}",
- "Status": "",
- "Hierarchy": "ui",
- "DeprecationMessage": ""
- },
- {
- "Name": "semanticTokens",
- "Type": "bool",
- "Doc": "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "false",
- "Status": "experimental",
- "Hierarchy": "ui",
- "DeprecationMessage": ""
- },
- {
- "Name": "noSemanticString",
- "Type": "bool",
- "Doc": "noSemanticString turns off the sending of the semantic token 'string'\n\nDeprecated: Use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "false",
- "Status": "experimental",
- "Hierarchy": "ui",
- "DeprecationMessage": "use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n"
- },
- {
- "Name": "noSemanticNumber",
- "Type": "bool",
- "Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n\nDeprecated: Use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "false",
- "Status": "experimental",
- "Hierarchy": "ui",
- "DeprecationMessage": "use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n"
- },
- {
- "Name": "semanticTokenTypes",
- "Type": "map[string]bool",
- "Doc": "semanticTokenTypes configures the semantic token types. It allows\ndisabling types by setting each value to false.\nBy default, all types are enabled.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
- },
- "EnumValues": null,
- "Default": "{}",
- "Status": "experimental",
- "Hierarchy": "ui",
- "DeprecationMessage": ""
- },
- {
- "Name": "semanticTokenModifiers",
- "Type": "map[string]bool",
- "Doc": "semanticTokenModifiers configures the semantic token modifiers. It allows\ndisabling modifiers by setting each value to false.\nBy default, all modifiers are enabled.\n",
- "EnumKeys": {
- "ValueType": "",
- "Keys": null
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4024\"",
+ "Doc": "Checking for impossible return value from a builtin function\n\nReturn values of the len and cap builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n if len(slice) \u003c 0 {\n fmt.Println(\"unreachable code\")\n }\n\nAvailable since\n 2021.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4025\"",
+ "Doc": "Integer division of literals that results in zero\n\nWhen dividing two integer constants, the result will\nalso be an integer. Thus, a division such as 2 / 3 results in 0.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.\n\nAvailable since\n 2021.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4026\"",
+ "Doc": "Go constants cannot express negative zero\n\nIn IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals -0.0 and 0.0 have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\nmath.Copysign function: math.Copysign(0, -1).\n\nAvailable since\n 2021.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4027\"",
+ "Doc": "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL\n\n(*net/url.URL).Query parses the current value of net/url.URL.RawQuery\nand returns it as a map of type net/url.Values. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's RawQuery.\n\nAs a consequence, the following code pattern is an expensive no-op:\nu.Query().Add(key, value).\n\nAvailable since\n 2021.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4028\"",
+ "Doc": "x % 1 is always zero\n\nAvailable since\n 2022.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4029\"",
+ "Doc": "Ineffective attempt at sorting slice\n\nsort.Float64Slice, sort.IntSlice, and sort.StringSlice are\ntypes, not functions. Doing x = sort.StringSlice(x) does nothing,\nespecially not sort any values. The correct usage is\nsort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),\nbut there are more convenient helpers, namely sort.Float64s,\nsort.Ints, and sort.Strings.\n\nAvailable since\n 2022.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4030\"",
+ "Doc": "Ineffective attempt at generating random number\n\nFunctions in the math/rand package that accept upper limits, such\nas Intn, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be \u003e= 0 and \u003c n – they\ndon't include n. rand.Intn(1) therefore doesn't generate 0\nor 1, it always generates 0.\n\nAvailable since\n 2022.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4031\"",
+ "Doc": "Checking never-nil value against nil\n\nAvailable since\n 2022.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA4032\"",
+ "Doc": "Comparing runtime.GOOS or runtime.GOARCH against impossible value\n\nAvailable since\n 2024.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5000\"",
+ "Doc": "Assignment to nil map\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5001\"",
+ "Doc": "Deferring Close before checking for a possible error\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5002\"",
+ "Doc": "The empty for loop ('for {}') spins and can block the scheduler\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5003\"",
+ "Doc": "Defers in infinite loops will never execute\n\nDefers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5004\"",
+ "Doc": "'for { select { ...' with an empty default branch spins\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5005\"",
+ "Doc": "The finalizer references the finalized object, preventing garbage collection\n\nA finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5007\"",
+ "Doc": "Infinite recursive call\n\nA function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5008\"",
+ "Doc": "Invalid struct tag\n\nAvailable since\n 2019.2\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5010\"",
+ "Doc": "Impossible type assertion\n\nSome type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.\n\nAvailable since\n 2020.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5011\"",
+ "Doc": "Possible nil pointer dereference\n\nA pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n func fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n }\n\n func TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n }\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to panic or log.Fatal. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n func Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n }\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n }\n\n func fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n }\n\nStaticcheck will flag the dereference of x, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n }\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.\n\nAvailable since\n 2020.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA5012\"",
+ "Doc": "Passing odd-sized slice to function expecting even size\n\nSome functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, strings.NewReplacer takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.\n\nAvailable since\n 2020.2\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA6000\"",
+ "Doc": "Using regexp.Match or related in a loop, should use regexp.Compile\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA6001\"",
+ "Doc": "Missing an optimization opportunity when indexing maps by byte slices\n\nMap keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes m[string(b)] and\nuses the data of b directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n k := string(b)\n println(m[k])\n println(m[k])\n\nwill be less efficient than\n\n println(m[string(b)])\n println(m[string(b)])\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA6002\"",
+ "Doc": "Storing non-pointer values in sync.Pool allocates memory\n\nA sync.Pool is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA6003\"",
+ "Doc": "Converting a string to a slice of runes before ranging over it\n\nYou may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n for _, r := range s {}\n\nand\n\n for _, r := range []rune(s) {}\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA6005\"",
+ "Doc": "Inefficient string comparison with strings.ToLower or strings.ToUpper\n\nConverting two strings to the same case and comparing them like so\n\n if strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n }\n\nis significantly more expensive than comparing them with\nstrings.EqualFold(s1, s2). This is due to memory usage as well as\ncomputational complexity.\n\nstrings.ToLower will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/\n\nAvailable since\n 2019.2\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA6006\"",
+ "Doc": "Using io.WriteString to write []byte\n\nUsing io.WriteString to write a slice of bytes, as in\n\n io.WriteString(w, string(b))\n\nis both unnecessary and inefficient. Converting from []byte to string\nhas to allocate and copy the data, and we could simply use w.Write(b)\ninstead.\n\nAvailable since\n 2024.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9001\"",
+ "Doc": "Defers in range loops may not run when you expect them to\n\nAvailable since\n 2017.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9002\"",
+ "Doc": "Using a non-octal os.FileMode that looks like it was meant to be in octal.\n\nAvailable since\n 2017.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9003\"",
+ "Doc": "Empty body in an if or else branch\n\nAvailable since\n 2017.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9004\"",
+ "Doc": "Only the first constant has an explicit type\n\nIn a constant declaration such as the following:\n\n const (\n First byte = 1\n Second = 2\n )\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n const (\n First byte = iota\n Second\n )\n\nwhere First and Second do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n const (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n )\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\nWrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n package pkg\n\n const (\n EnumFirst uint8 = 1\n EnumSecond = 2\n )\n\n func fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n }\n\nfails to compile with\n\n ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n\n\nLosing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n package main\n\n import \"fmt\"\n\n type Enum int\n\n func (e Enum) String() string {\n return \"an enum\"\n }\n\n const (\n EnumFirst Enum = 1\n EnumSecond = 2\n )\n\n func main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n }\n\nThis code will output\n\n an enum\n 2\n\nas EnumSecond has no explicit type, and thus defaults to int.\n\nAvailable since\n 2019.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9005\"",
+ "Doc": "Trying to marshal a struct with no public fields nor custom marshaling\n\nThe encoding/json and encoding/xml packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via MarshalJSON methods. It will also not\nflag empty structs.\n\nAvailable since\n 2019.2\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9006\"",
+ "Doc": "Dubious bit shifting of a fixed size integer value\n\nBit shifting a value past its size will always clear the value.\n\nFor instance:\n\n v := int8(42)\n v \u003e\u003e= 8\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n // Clear any value above 32 bits if integers are more than 32 bits.\n func f(i int) int {\n v := i \u003e\u003e 32\n v = v \u003c\u003c 32\n return i-v\n }\n\nAvailable since\n 2020.2\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9007\"",
+ "Doc": "Deleting a directory that shouldn't be deleted\n\nIt is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakenly using os.TempDir instead\nof ioutil.TempDir, or by forgetting to add a suffix to the result\nof os.UserHomeDir.\n\nWriting\n\n d := os.TempDir()\n defer os.RemoveAll(d)\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir\n\nAvailable since\n 2022.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9008\"",
+ "Doc": "else branch of a type assertion is probably not reading the right value\n\nWhen declaring variables as part of an if statement (like in 'if\nfoo := ...; foo {'), the same variables will also be in the scope of\nthe else branch. This means that in the following example\n\n if x, ok := x.(int); ok {\n // ...\n } else {\n fmt.Printf(\"unexpected type %T\", x)\n }\n\nx in the else branch will refer to the x from x, ok\n:=; it will not refer to the x that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so x in the else branch will always have the\nvalue 0 and the type int.\n\nAvailable since\n 2022.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"SA9009\"",
+ "Doc": "Ineffectual Go compiler directive\n\nA potential Go compiler directive was found, but is ineffectual as it begins\nwith whitespace.\n\nAvailable since\n 2024.1\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1000\"",
+ "Doc": "Incorrect or missing package comment\n\nPackages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://go.dev/wiki/CodeReviewComments#package-comments.\n\nAvailable since\n 2019.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1001\"",
+ "Doc": "Dot imports are discouraged\n\nDot imports that aren't in external test packages are discouraged.\n\nThe dot_import_whitelist option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n\u003e The import . form can be useful in tests that, due to circular\n\u003e dependencies, cannot be made part of the package being tested:\n\u003e \n\u003e package foo_test\n\u003e \n\u003e import (\n\u003e \"bar/testutil\" // also imports \"foo\"\n\u003e . \"foo\"\n\u003e )\n\u003e \n\u003e In this case, the test file cannot be in package foo because it\n\u003e uses bar/testutil, which imports foo. So we use the import .\n\u003e form to let the file pretend to be part of package foo even though\n\u003e it is not. Except for this one case, do not use import . in your\n\u003e programs. It makes the programs much harder to read because it is\n\u003e unclear whether a name like Quux is a top-level identifier in the\n\u003e current package or in an imported package.\n\nAvailable since\n 2019.1\n\nOptions\n dot_import_whitelist\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1003\"",
+ "Doc": "Poorly chosen identifier\n\nIdentifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://go.dev/doc/effective_go#package-names\n- https://go.dev/doc/effective_go#mixed-caps\n- https://go.dev/wiki/CodeReviewComments#initialisms\n- https://go.dev/wiki/CodeReviewComments#variable-names\n\nAvailable since\n 2019.1, non-default\n\nOptions\n initialisms\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1005\"",
+ "Doc": "Incorrectly formatted error string\n\nError strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n\u003e Error strings should not be capitalized (unless beginning with\n\u003e proper nouns or acronyms) or end with punctuation, since they are\n\u003e usually printed following other context. That is, use\n\u003e fmt.Errorf(\"something bad\") not fmt.Errorf(\"Something bad\"), so\n\u003e that log.Printf(\"Reading %s: %v\", filename, err) formats without a\n\u003e spurious capital letter mid-message.\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1006\"",
+ "Doc": "Poorly chosen receiver name\n\nQuoting Go Code Review Comments:\n\n\u003e The name of a method's receiver should be a reflection of its\n\u003e identity; often a one or two letter abbreviation of its type\n\u003e suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n\u003e names such as \"me\", \"this\" or \"self\", identifiers typical of\n\u003e object-oriented languages that place more emphasis on methods as\n\u003e opposed to functions. The name need not be as descriptive as that\n\u003e of a method argument, as its role is obvious and serves no\n\u003e documentary purpose. It can be very short as it will appear on\n\u003e almost every line of every method of the type; familiarity admits\n\u003e brevity. Be consistent, too: if you call the receiver \"c\" in one\n\u003e method, don't call it \"cl\" in another.\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1008\"",
+ "Doc": "A function's error value should be its last return value\n\nA function's error value should be its last return value.\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1011\"",
+ "Doc": "Poorly chosen name for variable of type time.Duration\n\ntime.Duration values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like 5 * time.Microsecond\nyields the value 5000. It is therefore not appropriate to suffix a\nvariable of type time.Duration with any time unit, such as Msec or\nMilli.\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1012\"",
+ "Doc": "Poorly chosen name for error variable\n\nError variables that are part of an API should be called errFoo or\nErrFoo.\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1013\"",
+ "Doc": "Should use constants for HTTP error codes, not magic numbers\n\nHTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The net/http\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.\n\nAvailable since\n 2019.1\n\nOptions\n http_status_code_whitelist\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1015\"",
+ "Doc": "A switch's default case should be the first or last case\n\nAvailable since\n 2019.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1016\"",
+ "Doc": "Use consistent method receiver names\n\nAvailable since\n 2019.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1017\"",
+ "Doc": "Don't use Yoda conditions\n\nYoda conditions are conditions of the kind 'if 42 == x', where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic 'if x == 42'.\n\nAvailable since\n 2019.2\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1018\"",
+ "Doc": "Avoid zero-width and control characters in string literals\n\nAvailable since\n 2019.2\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1019\"",
+ "Doc": "Importing the same package multiple times\n\nGo allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n import (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n )\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.\n\nAvailable since\n 2020.1\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1020\"",
+ "Doc": "The documentation of an exported function should start with the function's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1021\"",
+ "Doc": "The documentation of an exported type should start with type's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1022\"",
+ "Doc": "The documentation of an exported variable or constant should start with variable's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"ST1023\"",
+ "Doc": "Redundant type in variable declaration\n\nAvailable since\n 2021.1, non-default\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"appends\"",
+ "Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"asmdecl\"",
+ "Doc": "report mismatches between assembly files and Go declarations",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"assign\"",
+ "Doc": "check for useless assignments\n\nThis checker reports assignments of the form x = x or a[i] = a[i].\nThese are almost always useless, and even when they aren't they are\nusually a mistake.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"atomic\"",
+ "Doc": "check for common mistakes using the sync/atomic package\n\nThe atomic checker looks for assignment statements of the form:\n\n\tx = atomic.AddUint64(\u0026x, 1)\n\nwhich are not atomic.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"atomicalign\"",
+ "Doc": "check for non-64-bits-aligned arguments to sync/atomic functions",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"bools\"",
+ "Doc": "check for common mistakes involving boolean operators",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"buildtag\"",
+ "Doc": "check //go:build and // +build directives",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"cgocall\"",
+ "Doc": "detect some violations of the cgo pointer passing rules\n\nCheck for invalid cgo pointer passing.\nThis looks for code that uses cgo to call C code passing values\nwhose types are almost always invalid according to the cgo pointer\nsharing rules.\nSpecifically, it warns about attempts to pass a Go chan, map, func,\nor slice to C, either directly, or via a pointer, array, or struct.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"composites\"",
+ "Doc": "check for unkeyed composite literals\n\nThis analyzer reports a diagnostic for composite literals of struct\ntypes imported from another package that do not use the field-keyed\nsyntax. Such literals are fragile because the addition of a new field\n(even if unexported) to the struct will cause compilation to fail.\n\nAs an example,\n\n\terr = \u0026net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = \u0026net.DNSConfigError{Err: err}\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"copylocks\"",
+ "Doc": "check for locks erroneously passed by value\n\nInadvertently copying a value containing a lock, such as sync.Mutex or\nsync.WaitGroup, may cause both copies to malfunction. Generally such\nvalues should be referred to through a pointer.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"deepequalerrors\"",
+ "Doc": "check for calls of reflect.DeepEqual on error values\n\nThe deepequalerrors checker looks for calls of the form:\n\n reflect.DeepEqual(err1, err2)\n\nwhere err1 and err2 are errors. Using reflect.DeepEqual to compare\nerrors is discouraged.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"defers\"",
+ "Doc": "report common mistakes in defer statements\n\nThe defers analyzer reports a diagnostic when a defer statement would\nresult in a non-deferred call to time.Since, as experience has shown\nthat this is nearly always a mistake.\n\nFor example:\n\n\tstart := time.Now()\n\t...\n\tdefer recordLatency(time.Since(start)) // error: call to time.Since is not deferred\n\nThe correct code is:\n\n\tdefer func() { recordLatency(time.Since(start)) }()",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"deprecated\"",
+ "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"directive\"",
+ "Doc": "check Go toolchain directives such as //go:debug\n\nThis analyzer checks for problems with known Go toolchain directives\nin all Go source files in a package directory, even those excluded by\n//go:build constraints, and all non-Go source files too.\n\nFor //go:debug (see https://go.dev/doc/godebug), the analyzer checks\nthat the directives are placed only in Go source files, only above the\npackage comment, and only in package main or *_test.go files.\n\nSupport for other known directives may be added in the future.\n\nThis analyzer does not check //go:build, which is handled by the\nbuildtag analyzer.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"embed\"",
+ "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"errorsas\"",
+ "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"fillreturns\"",
+ "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"framepointer\"",
+ "Doc": "report assembly that clobbers the frame pointer before saving it",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"gofix\"",
+ "Doc": "apply fixes based on go:fix comment directives\n\nThe gofix analyzer inlines functions and constants that are marked for inlining.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"hostport\"",
+ "Doc": "check format of addresses passed to net.Dial\n\nThis analyzer flags code that produce network address strings using\nfmt.Sprintf, as in this example:\n\n addr := fmt.Sprintf(\"%s:%d\", host, 12345) // \"will not work with IPv6\"\n ...\n conn, err := net.Dial(\"tcp\", addr) // \"when passed to dial here\"\n\nThe analyzer suggests a fix to use the correct approach, a call to\nnet.JoinHostPort:\n\n addr := net.JoinHostPort(host, \"12345\")\n ...\n conn, err := net.Dial(\"tcp\", addr)\n\nA similar diagnostic and fix are produced for a format string of \"%s:%s\".\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"httpresponse\"",
+ "Doc": "check for mistakes using HTTP responses\n\nA common mistake when using the net/http package is to defer a function\ncall to close the http.Response Body before checking the error that\ndetermines whether the response is valid:\n\n\tresp, err := http.Head(url)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// (defer statement belongs here)\n\nThis checker helps uncover latent nil dereference bugs by reporting a\ndiagnostic for such mistakes.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"ifaceassert\"",
+ "Doc": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"infertypeargs\"",
+ "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"loopclosure\"",
+ "Doc": "check references to loop variables from within nested functions\n\nThis analyzer reports places where a function literal references the\niteration variable of an enclosing loop, and the loop calls the function\nin such a way (e.g. with go or defer) that it may outlive the loop\niteration and possibly observe the wrong value of the variable.\n\nNote: An iteration variable can only outlive a loop iteration in Go versions \u003c=1.21.\nIn Go 1.22 and later, the loop variable lifetimes changed to create a new\niteration variable per loop iteration. (See go.dev/issue/60078.)\n\nIn this example, all the deferred functions run after the loop has\ncompleted, so all observe the final value of v [\u003cgo1.22].\n\n\tfor _, v := range list {\n\t defer func() {\n\t use(v) // incorrect\n\t }()\n\t}\n\nOne fix is to create a new variable for each iteration of the loop:\n\n\tfor _, v := range list {\n\t v := v // new var per iteration\n\t defer func() {\n\t use(v) // ok\n\t }()\n\t}\n\nAfter Go version 1.22, the previous two for loops are equivalent\nand both are correct.\n\nThe next example uses a go statement and has a similar problem [\u003cgo1.22].\nIn addition, it has a data race because the loop updates v\nconcurrent with the goroutines accessing it.\n\n\tfor _, v := range elem {\n\t go func() {\n\t use(v) // incorrect, and a data race\n\t }()\n\t}\n\nA fix is the same as before. The checker also reports problems\nin goroutines started by golang.org/x/sync/errgroup.Group.\nA hard-to-spot variant of this form is common in parallel tests:\n\n\tfunc Test(t *testing.T) {\n\t for _, test := range tests {\n\t t.Run(test.name, func(t *testing.T) {\n\t t.Parallel()\n\t use(test) // incorrect, and a data race\n\t })\n\t }\n\t}\n\nThe t.Parallel() call causes the rest of the function to execute\nconcurrent with the loop [\u003cgo1.22].\n\nThe analyzer reports references only in the last statement,\nas it is not deep enough to understand the effects of subsequent\nstatements that might render the reference benign.\n(\"Last statement\" is defined recursively in compound\nstatements such as if, switch, and select.)\n\nSee: https://golang.org/doc/go_faq.html#closures_and_goroutines",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"lostcancel\"",
+ "Doc": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nWithDeadline and variants such as WithCancelCause must be called,\nor the new context will remain live until its parent context is cancelled.\n(The background context is never cancelled.)",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"modernize\"",
+ "Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go and its standard\nlibrary.\n\nEach diagnostic provides a fix. Our intent is that these fixes may\nbe safely applied en masse without changing the behavior of your\nprogram. In some cases the suggested fixes are imperfect and may\nlead to (for example) unused imports or unused local variables,\ncausing build breakage. However, these problems are generally\ntrivial to fix. We regard any modernizer whose fix changes program\nbehavior to have a serious bug and will endeavor to fix it.\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...\n\n(Do not use \"go get -tool\" to add gopls as a dependency of your\nmodule; gopls commands must be built from their release branch.)\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.\n\nChanges produced by this tool should be reviewed as usual before\nbeing merged. In some cases, a loop may be replaced by a simple\nfunction call, causing comments within the loop to be discarded.\nHuman judgment may be required to avoid losing comments of value.\n\nEach diagnostic reported by modernize has a specific category. (The\ncategories are listed below.) Diagnostics in some categories, such\nas \"efaceany\" (which replaces \"interface{}\" with \"any\" where it is\nsafe to do so) are particularly numerous. It may ease the burden of\ncode review to apply fixes in two passes, the first change\nconsisting only of fixes of category \"efaceany\", the second\nconsisting of all others. This can be achieved using the -category flag:\n\n\t$ modernize -category=efaceany -fix -test ./...\n\t$ modernize -category=-efaceany -fix -test ./...\n\nCategories of modernize diagnostic:\n\n - forvar: remove x := x variable declarations made unnecessary by the new semantics of loops in go1.22.\n\n - slicescontains: replace 'for i, elem := range s { if elem == needle { ...; break }'\n by a call to slices.Contains, added in go1.21.\n\n - minmax: replace an if/else conditional assignment by a call to\n the built-in min or max functions added in go1.21.\n\n - sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21.\n\n - efaceany: replace interface{} by the 'any' type added in go1.18.\n\n - slicesclone: replace append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21.\n\n - mapsloop: replace a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions from\n the maps package, added in go1.21.\n\n - fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19.\n\n - testingcontext: replace uses of context.WithCancel in tests\n with t.Context, added in go1.24.\n\n - omitzero: replace omitempty by omitzero on structs, added in go1.24.\n\n - bloop: replace \"for i := range b.N\" or \"for range b.N\" in a\n benchmark with \"for b.Loop()\", and remove any preceding calls\n to b.StopTimer, b.StartTimer, and b.ResetTimer.\n\n - slicesdelete: replace append(s[:i], s[i+1]...) by\n slices.Delete(s, i, i+1), added in go1.21.\n\n - rangeint: replace a 3-clause \"for i := 0; i \u003c n; i++\" loop by\n \"for i := range n\", added in go1.22.\n\n - stringsseq: replace Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq.\n\n - stringscutprefix: replace some uses of HasPrefix followed by TrimPrefix with CutPrefix,\n added to the strings package in go1.20.\n\n - waitgroup: replace old complex usages of sync.WaitGroup by less complex WaitGroup.Go method in go1.25.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"nilfunc\"",
+ "Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"nilness\"",
+ "Doc": "check for redundant or impossible nil comparisons\n\nThe nilness checker inspects the control-flow graph of each function in\na package and reports nil pointer dereferences, degenerate nil\npointers, and panics with nil values. A degenerate comparison is of the form\nx==nil or x!=nil where x is statically known to be nil or non-nil. These are\noften a mistake, especially in control flow related to errors. Panics with nil\nvalues are checked because they are not detectable by\n\n\tif r := recover(); r != nil {\n\nThis check reports conditions such as:\n\n\tif f == nil { // impossible condition (f is a function)\n\t}\n\nand:\n\n\tp := \u0026v\n\t...\n\tif p != nil { // tautological condition\n\t}\n\nand:\n\n\tif p == nil {\n\t\tprint(*p) // nil dereference\n\t}\n\nand:\n\n\tif p == nil {\n\t\tpanic(p)\n\t}\n\nSometimes the control flow may be quite complex, making bugs hard\nto spot. In the example below, the err.Error expression is\nguaranteed to panic because, after the first return, err must be\nnil. The intervening loop is just a distraction.\n\n\t...\n\terr := g.Wait()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpartialSuccess := false\n\tfor _, err := range errs {\n\t\tif err == nil {\n\t\t\tpartialSuccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif partialSuccess {\n\t\treportStatus(StatusMessage{\n\t\t\tCode: code.ERROR,\n\t\t\tDetail: err.Error(), // \"nil dereference in dynamic method call\"\n\t\t})\n\t\treturn nil\n\t}\n\n...",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"nonewvars\"",
+ "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"noresultvalues\"",
+ "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"printf\"",
+ "Doc": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"shadow\"",
+ "Doc": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"shift\"",
+ "Doc": "check for shifts that equal or exceed the width of the integer",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"sigchanyzer\"",
+ "Doc": "check for unbuffered channel of os.Signal\n\nThis checker reports call expression of the form\n\n\tsignal.Notify(c \u003c-chan os.Signal, sig ...os.Signal),\n\nwhere c is an unbuffered channel, which can be at risk of missing the signal.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"simplifycompositelit\"",
+ "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"simplifyrange\"",
+ "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"simplifyslice\"",
+ "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"slog\"",
+ "Doc": "check for invalid structured logging calls\n\nThe slog checker looks for calls to functions from the log/slog\npackage that take alternating key-value pairs. It reports calls\nwhere an argument in a key position is neither a string nor a\nslog.Attr, and where a final key is missing its value.\nFor example,it would report\n\n\tslog.Warn(\"message\", 11, \"k\") // slog.Warn arg \"11\" should be a string or a slog.Attr\n\nand\n\n\tslog.Info(\"message\", \"k1\", v1, \"k2\") // call to slog.Info missing a final value",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"sortslice\"",
+ "Doc": "check the argument type of sort.Slice\n\nsort.Slice requires an argument of a slice type. Check that\nthe interface{} value passed to sort.Slice is actually a slice.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"stdmethods\"",
+ "Doc": "check signature of methods of well-known interfaces\n\nSometimes a type may be intended to satisfy an interface but may fail to\ndo so because of a mistake in its method signature.\nFor example, the result of this WriteTo method should be (int64, error),\nnot error, to satisfy io.WriterTo:\n\n\ttype myWriterTo struct{...}\n\tfunc (myWriterTo) WriteTo(w io.Writer) error { ... }\n\nThis check ensures that each method whose name matches one of several\nwell-known interface methods from the standard library has the correct\nsignature for that interface.\n\nChecked method names include:\n\n\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"stdversion\"",
+ "Doc": "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"stringintconv\"",
+ "Doc": "check for string(int) conversions\n\nThis checker flags conversions of the form string(x) where x is an integer\n(but not byte or rune) type. Such conversions are discouraged because they\nreturn the UTF-8 representation of the Unicode code point x, and not a decimal\nstring representation of x as one might expect. Furthermore, if x denotes an\ninvalid code point, the conversion cannot be statically rejected.\n\nFor conversions that intend on using the code point, consider replacing them\nwith string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the\nstring representation of the value in the desired base.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"structtag\"",
+ "Doc": "check that struct field tags conform to reflect.StructTag.Get\n\nAlso report certain struct tags (json, xml) used with unexported fields.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"testinggoroutine\"",
+ "Doc": "report calls to (*testing.T).Fatal from goroutines started by a test\n\nFunctions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and\nSkip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.\nThis checker detects calls to these functions that occur within a goroutine\nstarted by the test. For example:\n\n\tfunc TestFoo(t *testing.T) {\n\t go func() {\n\t t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n\t }()\n\t}",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"tests\"",
+ "Doc": "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark, Fuzzing and Example functions checking\nmalformed names, wrong signatures and examples documenting non-existent\nidentifiers.\n\nPlease see the documentation for package testing in golang.org/pkg/testing\nfor the conventions that are enforced for Tests, Benchmarks, and Examples.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"timeformat\"",
+ "Doc": "check for calls of (time.Time).Format or time.Parse with 2006-02-01\n\nThe timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)\nformat. Internationally, \"yyyy-dd-mm\" does not occur in common calendar date\nstandards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unmarshal\"",
+ "Doc": "report passing non-pointer or non-interface values to unmarshal\n\nThe unmarshal analysis reports calls to functions such as json.Unmarshal\nin which the argument type is not a pointer or an interface.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unreachable\"",
+ "Doc": "check for unreachable code\n\nThe unreachable analyzer finds statements that execution can never reach\nbecause they are preceded by a return statement, a call to panic, an\ninfinite loop, or similar constructs.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unsafeptr\"",
+ "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unusedfunc\"",
+ "Doc": "check for unused functions and methods\n\nThe unusedfunc analyzer reports functions and methods that are\nnever referenced outside of their own declaration.\n\nA function is considered unused if it is unexported and not\nreferenced (except within its own declaration).\n\nA method is considered unused if it is unexported, not referenced\n(except within its own declaration), and its name does not match\nthat of any method of an interface type declared within the same\npackage.\n\nThe tool may report false positives in some situations, for\nexample:\n\n - For a declaration of an unexported function that is referenced\n from another package using the go:linkname mechanism, if the\n declaration's doc comment does not also have a go:linkname\n comment.\n\n (Such code is in any case strongly discouraged: linkname\n annotations, if they must be used at all, should be used on both\n the declaration and the alias.)\n\n - For compiler intrinsics in the \"runtime\" package that, though\n never referenced, are known to the compiler and are called\n indirectly by compiled object code.\n\n - For functions called only from assembly.\n\n - For functions called only from files whose build tags are not\n selected in the current build configuration.\n\nSee https://github.com/golang/go/issues/71686 for discussion of\nthese limitations.\n\nThe unusedfunc algorithm is not as precise as the\ngolang.org/x/tools/cmd/deadcode tool, but it has the advantage that\nit runs within the modular analysis framework, enabling near\nreal-time feedback within gopls.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unusedparams\"",
+ "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.\n\nThis analyzer ignores generated code.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unusedresult\"",
+ "Doc": "check for unused results of calls to some functions\n\nSome functions like fmt.Errorf return a result and have no side\neffects, so it is always a mistake to discard the result. Other\nfunctions may return an error that must not be ignored, or a cleanup\noperation that must be called. This analyzer reports calls to\nfunctions like these when the result of the call is ignored.\n\nThe set of functions may be controlled using flags.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unusedvariable\"",
+ "Doc": "check for unused variables and suggest fixes",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"unusedwrite\"",
+ "Doc": "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"waitgroup\"",
+ "Doc": "check for misuses of sync.WaitGroup\n\nThis analyzer detects mistaken calls to the (*sync.WaitGroup).Add\nmethod from inside a new goroutine, causing Add to race with Wait:\n\n\t// WRONG\n\tvar wg sync.WaitGroup\n\tgo func() {\n\t wg.Add(1) // \"WaitGroup.Add called from inside new goroutine\"\n\t defer wg.Done()\n\t ...\n\t}()\n\twg.Wait() // (may return prematurely before new goroutine starts)\n\nThe correct code calls Add before starting the goroutine:\n\n\t// RIGHT\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\twg.Wait()",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"yield\"",
+ "Doc": "report calls to yield where the result is ignored\n\nAfter a yield function returns false, the caller should not call\nthe yield function again; generally the iterator should return\npromptly.\n\nThis example fails to check the result of the call to yield,\ncausing this analyzer to report a diagnostic:\n\n\tyield(1) // yield may be called again (on L2) after returning false\n\tyield(2)\n\nThe corrected code is either this:\n\n\tif yield(1) { yield(2) }\n\nor simply:\n\n\t_ = yield(1) \u0026\u0026 yield(2)\n\nIt is not always a mistake to ignore the result of yield.\nFor example, this is a valid single-element iterator:\n\n\tyield(1) // ok to ignore result\n\treturn\n\nIt is only a mistake when the yield call that returned false may be\nfollowed by another call.",
+ "Default": "true",
+ "Status": ""
+ }
+ ]
+ },
+ "EnumValues": null,
+ "Default": "{}",
+ "Status": "",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "staticcheck",
+ "Type": "bool",
+ "Doc": "staticcheck configures the default set of analyses staticcheck.io.\nThese analyses are documented on\n[Staticcheck's website](https://staticcheck.io/docs/checks/).\n\nThe \"staticcheck\" option has three values:\n- false: disable all staticcheck analyzers\n- true: enable all staticcheck analyzers\n- unset: enable a subset of staticcheck analyzers\n selected by gopls maintainers for runtime efficiency\n and analytic precision.\n\nRegardless of this setting, individual analyzers can be\nselectively enabled or disabled using the `analyses` setting.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "false",
+ "Status": "experimental",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "staticcheckProvided",
+ "Type": "bool",
+ "Doc": "",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "false",
+ "Status": "experimental",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "annotations",
+ "Type": "map[enum]bool",
+ "Doc": "annotations specifies the various kinds of compiler\noptimization details that should be reported as diagnostics\nwhen enabled for a package by the \"Toggle compiler\noptimization details\" (`gopls.gc_details`) command.\n\n(Some users care only about one kind of annotation in their\nprofiling efforts. More importantly, in large packages, the\nnumber of annotations can sometimes overwhelm the user\ninterface and exceed the per-file diagnostic limit.)\n\nTODO(adonovan): rename this field to CompilerOptDetail.\n",
+ "EnumKeys": {
+ "ValueType": "bool",
+ "Keys": [
+ {
+ "Name": "\"bounds\"",
+ "Doc": "`\"bounds\"` controls bounds checking diagnostics.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"escape\"",
+ "Doc": "`\"escape\"` controls diagnostics about escape choices.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"inline\"",
+ "Doc": "`\"inline\"` controls diagnostics about inlining choices.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"nil\"",
+ "Doc": "`\"nil\"` controls nil checks.\n",
+ "Default": "true",
+ "Status": ""
+ }
+ ]
+ },
+ "EnumValues": null,
+ "Default": "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}",
+ "Status": "",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "vulncheck",
+ "Type": "enum",
+ "Doc": "vulncheck enables vulnerability scanning.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": [
+ {
+ "Value": "\"Imports\"",
+ "Doc": "`\"Imports\"`: In Imports mode, `gopls` will report vulnerabilities that affect packages\ndirectly and indirectly used by the analyzed main module.\n",
+ "Status": ""
+ },
+ {
+ "Value": "\"Off\"",
+ "Doc": "`\"Off\"`: Disable vulnerability analysis.\n",
+ "Status": ""
+ }
+ ],
+ "Default": "\"Off\"",
+ "Status": "experimental",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "diagnosticsDelay",
+ "Type": "time.Duration",
+ "Doc": "diagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "\"1s\"",
+ "Status": "advanced",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "diagnosticsTrigger",
+ "Type": "enum",
+ "Doc": "diagnosticsTrigger controls when to run diagnostics.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": [
+ {
+ "Value": "\"Edit\"",
+ "Doc": "`\"Edit\"`: Trigger diagnostics on file edit and save. (default)\n",
+ "Status": ""
+ },
+ {
+ "Value": "\"Save\"",
+ "Doc": "`\"Save\"`: Trigger diagnostics only on file save. Events like initial workspace load\nor configuration change will still trigger diagnostics.\n",
+ "Status": ""
+ }
+ ],
+ "Default": "\"Edit\"",
+ "Status": "experimental",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "analysisProgressReporting",
+ "Type": "bool",
+ "Doc": "analysisProgressReporting controls whether gopls sends progress\nnotifications when construction of its index of analysis facts is taking a\nlong time. Cancelling these notifications will cancel the indexing task,\nthough it will restart after the next change in the workspace.\n\nWhen a package is opened for the first time and heavyweight analyses such as\nstaticcheck are enabled, it can take a while to construct the index of\nanalysis facts for all its dependencies. The index is cached in the\nfilesystem, so subsequent analysis should be faster.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "true",
+ "Status": "",
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "hints",
+ "Type": "map[enum]bool",
+ "Doc": "hints specify inlay hints that users want to see. A full list of hints\nthat gopls uses can be found in\n[inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).\n",
+ "EnumKeys": {
+ "ValueType": "bool",
+ "Keys": [
+ {
+ "Name": "\"assignVariableTypes\"",
+ "Doc": "`\"assignVariableTypes\"` controls inlay hints for variable types in assign statements:\n```go\n\ti/* int*/, j/* int*/ := 0, len(r)-1\n```\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"compositeLiteralFields\"",
+ "Doc": "`\"compositeLiteralFields\"` inlay hints for composite literal field names:\n```go\n\t{/*in: */\"Hello, world\", /*want: */\"dlrow ,olleH\"}\n```\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"compositeLiteralTypes\"",
+ "Doc": "`\"compositeLiteralTypes\"` controls inlay hints for composite literal types:\n```go\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}\n```\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"constantValues\"",
+ "Doc": "`\"constantValues\"` controls inlay hints for constant values:\n```go\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)\n```\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"functionTypeParameters\"",
+ "Doc": "`\"functionTypeParameters\"` inlay hints for implicit type parameters on generic functions:\n```go\n\tmyFoo/*[int, string]*/(1, \"hello\")\n```\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"parameterNames\"",
+ "Doc": "`\"parameterNames\"` controls inlay hints for parameter names:\n```go\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)\n```\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"rangeVariableTypes\"",
+ "Doc": "`\"rangeVariableTypes\"` controls inlay hints for variable types in range statements:\n```go\n\tfor k/* int*/, v/* string*/ := range []string{} {\n\t\tfmt.Println(k, v)\n\t}\n```\n",
+ "Default": "false",
+ "Status": ""
+ }
+ ]
+ },
+ "EnumValues": null,
+ "Default": "{}",
+ "Status": "experimental",
+ "Hierarchy": "ui.inlayhint",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "codelenses",
+ "Type": "map[enum]bool",
+ "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n }\n...\n}\n```\n",
+ "EnumKeys": {
+ "ValueType": "bool",
+ "Keys": [
+ {
+ "Name": "\"generate\"",
+ "Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"regenerate_cgo\"",
+ "Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"run_govulncheck\"",
+ "Doc": "`\"run_govulncheck\"`: Run govulncheck (legacy)\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+ "Default": "false",
+ "Status": "experimental"
+ },
+ {
+ "Name": "\"test\"",
+ "Doc": "`\"test\"`: Run tests and benchmarks\n\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
+ "Default": "false",
+ "Status": ""
+ },
+ {
+ "Name": "\"tidy\"",
+ "Doc": "`\"tidy\"`: Tidy go.mod file\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"upgrade_dependency\"",
+ "Doc": "`\"upgrade_dependency\"`: Update dependencies\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"vendor\"",
+ "Doc": "`\"vendor\"`: Update vendor directory\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"vulncheck\"",
+ "Doc": "`\"vulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+ "Default": "false",
+ "Status": "experimental"
+ }
+ ]
+ },
+ "EnumValues": null,
+ "Default": "{\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":false,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}",
+ "Status": "",
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "semanticTokens",
+ "Type": "bool",
+ "Doc": "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "false",
+ "Status": "experimental",
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "noSemanticString",
+ "Type": "bool",
+ "Doc": "noSemanticString turns off the sending of the semantic token 'string'\n\nDeprecated: Use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "false",
+ "Status": "experimental",
+ "Hierarchy": "ui",
+ "DeprecationMessage": "use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n"
+ },
+ {
+ "Name": "noSemanticNumber",
+ "Type": "bool",
+ "Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n\nDeprecated: Use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "false",
+ "Status": "experimental",
+ "Hierarchy": "ui",
+ "DeprecationMessage": "use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n"
+ },
+ {
+ "Name": "semanticTokenTypes",
+ "Type": "map[string]bool",
+ "Doc": "semanticTokenTypes configures the semantic token types. It allows\ndisabling types by setting each value to false.\nBy default, all types are enabled.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
+ },
+ "EnumValues": null,
+ "Default": "{}",
+ "Status": "experimental",
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
+ },
+ {
+ "Name": "semanticTokenModifiers",
+ "Type": "map[string]bool",
+ "Doc": "semanticTokenModifiers configures the semantic token modifiers. It allows\ndisabling modifiers by setting each value to false.\nBy default, all modifiers are enabled.\n",
+ "EnumKeys": {
+ "ValueType": "",
+ "Keys": null
},
"EnumValues": null,
"Default": "{}",
@@ -1121,71 +2065,1001 @@
},
"Lenses": [
{
- "FileType": "Go",
- "Lens": "generate",
- "Title": "Run `go generate`",
- "Doc": "\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
- "Default": true,
- "Status": ""
+ "FileType": "Go",
+ "Lens": "generate",
+ "Title": "Run `go generate`",
+ "Doc": "\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
+ "Default": true,
+ "Status": ""
+ },
+ {
+ "FileType": "Go",
+ "Lens": "regenerate_cgo",
+ "Title": "Re-generate cgo declarations",
+ "Doc": "\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
+ "Default": true,
+ "Status": ""
+ },
+ {
+ "FileType": "Go",
+ "Lens": "test",
+ "Title": "Run tests and benchmarks",
+ "Doc": "\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
+ "Default": false,
+ "Status": ""
+ },
+ {
+ "FileType": "go.mod",
+ "Lens": "run_govulncheck",
+ "Title": "Run govulncheck (legacy)",
+ "Doc": "\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+ "Default": false,
+ "Status": "experimental"
+ },
+ {
+ "FileType": "go.mod",
+ "Lens": "tidy",
+ "Title": "Tidy go.mod file",
+ "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
+ "Default": true,
+ "Status": ""
+ },
+ {
+ "FileType": "go.mod",
+ "Lens": "upgrade_dependency",
+ "Title": "Update dependencies",
+ "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
+ "Default": true,
+ "Status": ""
+ },
+ {
+ "FileType": "go.mod",
+ "Lens": "vendor",
+ "Title": "Update vendor directory",
+ "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
+ "Default": true,
+ "Status": ""
+ },
+ {
+ "FileType": "go.mod",
+ "Lens": "vulncheck",
+ "Title": "Run govulncheck",
+ "Doc": "\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+ "Default": false,
+ "Status": "experimental"
+ }
+ ],
+ "Analyzers": [
+ {
+ "Name": "QF1001",
+ "Doc": "Apply De Morgan's law\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1001",
+ "Default": false
+ },
+ {
+ "Name": "QF1002",
+ "Doc": "Convert untagged switch to tagged switch\n\nAn untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n switch {\n case x == 1 || x == 2, x == 3:\n ...\n case x == 4:\n ...\n default:\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2, 3:\n ...\n case 4:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1002",
+ "Default": true
+ },
+ {
+ "Name": "QF1003",
+ "Doc": "Convert if/else-if chain to tagged switch\n\nA series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n if x == 1 || x == 2 {\n ...\n } else if x == 3 {\n ...\n } else {\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2:\n ...\n case 3:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1003",
+ "Default": true
+ },
+ {
+ "Name": "QF1004",
+ "Doc": "Use strings.ReplaceAll instead of strings.Replace with n == -1\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1004",
+ "Default": true
+ },
+ {
+ "Name": "QF1005",
+ "Doc": "Expand call to math.Pow\n\nSome uses of math.Pow can be simplified to basic multiplication.\n\nBefore:\n\n math.Pow(x, 2)\n\nAfter:\n\n x * x\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1005",
+ "Default": false
+ },
+ {
+ "Name": "QF1006",
+ "Doc": "Lift if+break into loop condition\n\nBefore:\n\n for {\n if done {\n break\n }\n ...\n }\n\nAfter:\n\n for !done {\n ...\n }\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1006",
+ "Default": false
+ },
+ {
+ "Name": "QF1007",
+ "Doc": "Merge conditional assignment into variable declaration\n\nBefore:\n\n x := false\n if someCondition {\n x = true\n }\n\nAfter:\n\n x := someCondition\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1007",
+ "Default": false
+ },
+ {
+ "Name": "QF1008",
+ "Doc": "Omit embedded fields from selector expression\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1008",
+ "Default": false
+ },
+ {
+ "Name": "QF1009",
+ "Doc": "Use time.Time.Equal instead of == operator\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1009",
+ "Default": true
+ },
+ {
+ "Name": "QF1010",
+ "Doc": "Convert slice of bytes to string when printing it\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1010",
+ "Default": true
+ },
+ {
+ "Name": "QF1011",
+ "Doc": "Omit redundant type from variable declaration\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#",
+ "Default": false
+ },
+ {
+ "Name": "QF1012",
+ "Doc": "Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#QF1012",
+ "Default": true
+ },
+ {
+ "Name": "S1000",
+ "Doc": "Use plain channel send or receive instead of single-case select\n\nSelect statements with a single case can be replaced with a simple\nsend or receive.\n\nBefore:\n\n select {\n case x := \u003c-ch:\n fmt.Println(x)\n }\n\nAfter:\n\n x := \u003c-ch\n fmt.Println(x)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1000",
+ "Default": true
+ },
+ {
+ "Name": "S1001",
+ "Doc": "Replace for loop with call to copy\n\nUse copy() for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.\n\nBefore:\n\n for i, x := range src {\n dst[i] = x\n }\n\nAfter:\n\n copy(dst, src)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1001",
+ "Default": true
+ },
+ {
+ "Name": "S1002",
+ "Doc": "Omit comparison with boolean constant\n\nBefore:\n\n if x == true {}\n\nAfter:\n\n if x {}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1002",
+ "Default": false
+ },
+ {
+ "Name": "S1003",
+ "Doc": "Replace call to strings.Index with strings.Contains\n\nBefore:\n\n if strings.Index(x, y) != -1 {}\n\nAfter:\n\n if strings.Contains(x, y) {}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1003",
+ "Default": true
+ },
+ {
+ "Name": "S1004",
+ "Doc": "Replace call to bytes.Compare with bytes.Equal\n\nBefore:\n\n if bytes.Compare(x, y) == 0 {}\n\nAfter:\n\n if bytes.Equal(x, y) {}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1004",
+ "Default": true
+ },
+ {
+ "Name": "S1005",
+ "Doc": "Drop unnecessary use of the blank identifier\n\nIn many cases, assigning to the blank identifier is unnecessary.\n\nBefore:\n\n for _ = range s {}\n x, _ = someMap[key]\n _ = \u003c-ch\n\nAfter:\n\n for range s{}\n x = someMap[key]\n \u003c-ch\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1005",
+ "Default": false
+ },
+ {
+ "Name": "S1006",
+ "Doc": "Use 'for { ... }' for infinite loops\n\nFor infinite loops, using for { ... } is the most idiomatic choice.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1006",
+ "Default": false
+ },
+ {
+ "Name": "S1007",
+ "Doc": "Simplify regular expression by using raw string literal\n\nRaw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.\n\nBefore:\n\n regexp.Compile(\"\\\\A(\\\\w+) profile: total \\\\d+\\\\n\\\\z\")\n\nAfter:\n\n regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1007",
+ "Default": true
+ },
+ {
+ "Name": "S1008",
+ "Doc": "Simplify returning boolean expression\n\nBefore:\n\n if \u003cexpr\u003e {\n return true\n }\n return false\n\nAfter:\n\n return \u003cexpr\u003e\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1008",
+ "Default": false
+ },
+ {
+ "Name": "S1009",
+ "Doc": "Omit redundant nil check on slices, maps, and channels\n\nThe len function is defined for all slices, maps, and\nchannels, even nil ones, which have a length of zero. It is not necessary to\ncheck for nil before checking that their length is not zero.\n\nBefore:\n\n if x != nil \u0026\u0026 len(x) != 0 {}\n\nAfter:\n\n if len(x) != 0 {}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1009",
+ "Default": true
+ },
+ {
+ "Name": "S1010",
+ "Doc": "Omit default slice index\n\nWhen slicing, the second index defaults to the length of the value,\nmaking s[n:len(s)] and s[n:] equivalent.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1010",
+ "Default": true
+ },
+ {
+ "Name": "S1011",
+ "Doc": "Use a single append to concatenate two slices\n\nBefore:\n\n for _, e := range y {\n x = append(x, e)\n }\n \n for i := range y {\n x = append(x, y[i])\n }\n \n for i := range y {\n v := y[i]\n x = append(x, v)\n }\n\nAfter:\n\n x = append(x, y...)\n x = append(x, y...)\n x = append(x, y...)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1011",
+ "Default": false
+ },
+ {
+ "Name": "S1012",
+ "Doc": "Replace time.Now().Sub(x) with time.Since(x)\n\nThe time.Since helper has the same effect as using time.Now().Sub(x)\nbut is easier to read.\n\nBefore:\n\n time.Now().Sub(x)\n\nAfter:\n\n time.Since(x)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1012",
+ "Default": true
+ },
+ {
+ "Name": "S1016",
+ "Doc": "Use a type conversion instead of manually copying struct fields\n\nTwo struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.\n\nBefore:\n\n var x T1\n y := T2{\n Field1: x.Field1,\n Field2: x.Field2,\n }\n\nAfter:\n\n var x T1\n y := T2(x)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1016",
+ "Default": false
+ },
+ {
+ "Name": "S1017",
+ "Doc": "Replace manual trimming with strings.TrimPrefix\n\nInstead of using strings.HasPrefix and manual slicing, use the\nstrings.TrimPrefix function. If the string doesn't start with the\nprefix, the original string will be returned. Using strings.TrimPrefix\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.\n\nBefore:\n\n if strings.HasPrefix(str, prefix) {\n str = str[len(prefix):]\n }\n\nAfter:\n\n str = strings.TrimPrefix(str, prefix)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1017",
+ "Default": true
+ },
+ {
+ "Name": "S1018",
+ "Doc": "Use 'copy' for sliding elements\n\ncopy() permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.\n\nBefore:\n\n for i := 0; i \u003c n; i++ {\n bs[i] = bs[offset+i]\n }\n\nAfter:\n\n copy(bs[:n], bs[offset:])\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1018",
+ "Default": true
+ },
+ {
+ "Name": "S1019",
+ "Doc": "Simplify 'make' call by omitting redundant arguments\n\nThe 'make' function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1019",
+ "Default": true
+ },
+ {
+ "Name": "S1020",
+ "Doc": "Omit redundant nil check in type assertion\n\nBefore:\n\n if _, ok := i.(T); ok \u0026\u0026 i != nil {}\n\nAfter:\n\n if _, ok := i.(T); ok {}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1020",
+ "Default": true
+ },
+ {
+ "Name": "S1021",
+ "Doc": "Merge variable declaration and assignment\n\nBefore:\n\n var x uint\n x = 1\n\nAfter:\n\n var x uint = 1\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1021",
+ "Default": false
+ },
+ {
+ "Name": "S1023",
+ "Doc": "Omit redundant control flow\n\nFunctions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1023",
+ "Default": true
+ },
+ {
+ "Name": "S1024",
+ "Doc": "Replace x.Sub(time.Now()) with time.Until(x)\n\nThe time.Until helper has the same effect as using x.Sub(time.Now())\nbut is easier to read.\n\nBefore:\n\n x.Sub(time.Now())\n\nAfter:\n\n time.Until(x)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1024",
+ "Default": true
+ },
+ {
+ "Name": "S1025",
+ "Doc": "Don't use fmt.Sprintf(\"%s\", x) unnecessarily\n\nIn many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n type T1 string\n type T2 int\n\n func (T2) String() string { return \"Hello, world\" }\n\n var x string\n var y T1\n var z T2\n\nwe can simplify\n\n fmt.Sprintf(\"%s\", x)\n fmt.Sprintf(\"%s\", y)\n fmt.Sprintf(\"%s\", z)\n\nto\n\n x\n string(y)\n z.String()\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1025",
+ "Default": false
+ },
+ {
+ "Name": "S1028",
+ "Doc": "Simplify error construction with fmt.Errorf\n\nBefore:\n\n errors.New(fmt.Sprintf(...))\n\nAfter:\n\n fmt.Errorf(...)\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1028",
+ "Default": true
+ },
+ {
+ "Name": "S1029",
+ "Doc": "Range over the string directly\n\nRanging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.\n\nBefore:\n\n for _, r := range []rune(s) {}\n\nAfter:\n\n for _, r := range s {}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1029",
+ "Default": false
+ },
+ {
+ "Name": "S1030",
+ "Doc": "Use bytes.Buffer.String or bytes.Buffer.Bytes\n\nbytes.Buffer has both a String and a Bytes method. It is almost never\nnecessary to use string(buf.Bytes()) or []byte(buf.String()) – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\nm[string(buf.Bytes())] is more efficient than m[buf.String()].\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1030",
+ "Default": true
+ },
+ {
+ "Name": "S1031",
+ "Doc": "Omit redundant nil check around loop\n\nYou can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.\n\nBefore:\n\n if s != nil {\n for _, x := range s {\n ...\n }\n }\n\nAfter:\n\n for _, x := range s {\n ...\n }\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1031",
+ "Default": true
+ },
+ {
+ "Name": "S1032",
+ "Doc": "Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)\n\nThe sort.Ints, sort.Float64s and sort.Strings functions are easier to\nread than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))\nand sort.Sort(sort.StringSlice(x)).\n\nBefore:\n\n sort.Sort(sort.StringSlice(x))\n\nAfter:\n\n sort.Strings(x)\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1032",
+ "Default": true
+ },
+ {
+ "Name": "S1033",
+ "Doc": "Unnecessary guard around call to 'delete'\n\nCalling delete on a nil map is a no-op.\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1033",
+ "Default": true
+ },
+ {
+ "Name": "S1034",
+ "Doc": "Use result of type assertion to simplify cases\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1034",
+ "Default": true
+ },
+ {
+ "Name": "S1035",
+ "Doc": "Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header\n\nThe methods on net/http.Header, namely Add, Del, Get\nand Set, already canonicalize the given header name.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1035",
+ "Default": true
+ },
+ {
+ "Name": "S1036",
+ "Doc": "Unnecessary guard around map access\n\nWhen accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n if _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n } else {\n m[\"foo\"] = []string{\"bar\"}\n }\n\ncan be simplified to\n\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n\nand\n\n if _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n } else {\n m2[\"k\"] = 4\n }\n\ncan be simplified to\n\n m[\"k\"] += 4\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1036",
+ "Default": true
+ },
+ {
+ "Name": "S1037",
+ "Doc": "Elaborate way of sleeping\n\nUsing a select statement with a single case receiving\nfrom the result of time.After is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1037",
+ "Default": true
+ },
+ {
+ "Name": "S1038",
+ "Doc": "Unnecessarily complex way of printing formatted string\n\nInstead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1038",
+ "Default": true
+ },
+ {
+ "Name": "S1039",
+ "Doc": "Unnecessary use of fmt.Sprint\n\nCalling fmt.Sprint with a single string argument is unnecessary\nand identical to using the string directly.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1039",
+ "Default": true
+ },
+ {
+ "Name": "S1040",
+ "Doc": "Type assertion to current type\n\nThe type assertion x.(SomeInterface), when x already has type\nSomeInterface, can only fail if x is nil. Usually, this is\nleft-over code from when x had a different type and you can safely\ndelete the type assertion. If you want to check that x is not nil,\nconsider being explicit and using an actual if x == nil comparison\ninstead of relying on the type assertion panicking.\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#S1040",
+ "Default": true
+ },
+ {
+ "Name": "SA1000",
+ "Doc": "Invalid regular expression\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1000",
+ "Default": false
+ },
+ {
+ "Name": "SA1001",
+ "Doc": "Invalid template\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1001",
+ "Default": true
+ },
+ {
+ "Name": "SA1002",
+ "Doc": "Invalid format in time.Parse\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1002",
+ "Default": false
+ },
+ {
+ "Name": "SA1003",
+ "Doc": "Unsupported argument to functions in encoding/binary\n\nThe encoding/binary package can only serialize types with known sizes.\nThis precludes the use of the int and uint types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, bool wasn't supported, either.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1003",
+ "Default": false
+ },
+ {
+ "Name": "SA1004",
+ "Doc": "Suspiciously small untyped constant in time.Sleep\n\nThe time.Sleep function takes a time.Duration as its only argument.\nDurations are expressed in nanoseconds. Thus, calling time.Sleep(1)\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe time package provides constants such as time.Second to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example 5 * time.Second for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\nn * time.Nanosecond to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1004",
+ "Default": true
+ },
+ {
+ "Name": "SA1005",
+ "Doc": "Invalid first argument to exec.Command\n\nos/exec runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n ls / /tmp\n\nwould be\n\n exec.Command(\"ls\", \"/\", \"/tmp\")\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a /bin/sh program:\n\n exec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1005",
+ "Default": true
+ },
+ {
+ "Name": "SA1007",
+ "Doc": "Invalid URL in net/url.Parse\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1007",
+ "Default": false
+ },
+ {
+ "Name": "SA1008",
+ "Doc": "Non-canonical key in http.Header map\n\nKeys in http.Header maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\nhttp.Header.Add and http.Header.Del convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating http.Header maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n h := http.Header{}\n h[\"etag\"] = []string{\"1234\"}\n h.Add(\"etag\", \"5678\")\n fmt.Println(h)\n\n // Output:\n // map[Etag:[5678] etag:[1234]]\n\nThe easiest way of obtaining the canonical form of a key is to use\nhttp.CanonicalHeaderKey.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1008",
+ "Default": true
+ },
+ {
+ "Name": "SA1010",
+ "Doc": "(*regexp.Regexp).FindAll called with n == 0, which will always return zero results\n\nIf n \u003e= 0, the function returns at most n matches/submatches. To\nreturn all results, specify a negative number.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1010",
+ "Default": false
+ },
+ {
+ "Name": "SA1011",
+ "Doc": "Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1011",
+ "Default": false
+ },
+ {
+ "Name": "SA1012",
+ "Doc": "A nil context.Context is being passed to a function, consider using context.TODO instead\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1012",
+ "Default": true
+ },
+ {
+ "Name": "SA1013",
+ "Doc": "io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1013",
+ "Default": true
+ },
+ {
+ "Name": "SA1014",
+ "Doc": "Non-pointer value passed to Unmarshal or Decode\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1014",
+ "Default": false
+ },
+ {
+ "Name": "SA1015",
+ "Doc": "Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions\n\nBefore Go 1.23, time.Tickers had to be closed to be able to be garbage\ncollected. Since time.Tick doesn't make it possible to close the underlying\nticker, using it repeatedly would leak memory.\n\nGo 1.23 fixes this by allowing tickers to be collected even if they weren't closed.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1015",
+ "Default": false
+ },
+ {
+ "Name": "SA1016",
+ "Doc": "Trapping a signal that cannot be trapped\n\nNot all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1016",
+ "Default": true
+ },
+ {
+ "Name": "SA1017",
+ "Doc": "Channels used with os/signal.Notify should be buffered\n\nThe os/signal package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1017",
+ "Default": false
+ },
+ {
+ "Name": "SA1018",
+ "Doc": "strings.Replace called with n == 0, which does nothing\n\nWith n == 0, zero instances will be replaced. To replace all\ninstances, use a negative number, or use strings.ReplaceAll.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1018",
+ "Default": false
+ },
+ {
+ "Name": "SA1020",
+ "Doc": "Using an invalid host:port pair with a net.Listen-related function\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1020",
+ "Default": false
+ },
+ {
+ "Name": "SA1021",
+ "Doc": "Using bytes.Equal to compare two net.IP\n\nA net.IP stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two net.IPs, the net.IP.Equal method should\nbe used, as it takes both representations into account.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1021",
+ "Default": false
+ },
+ {
+ "Name": "SA1023",
+ "Doc": "Modifying the buffer in an io.Writer implementation\n\nWrite must not modify the slice data, even temporarily.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1023",
+ "Default": false
+ },
+ {
+ "Name": "SA1024",
+ "Doc": "A string cutset contains duplicate characters\n\nThe strings.TrimLeft and strings.TrimRight functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n strings.TrimLeft(\"42133word\", \"1234\")\n\nwill result in the string \"word\" – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use strings.TrimPrefix instead.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1024",
+ "Default": false
+ },
+ {
+ "Name": "SA1025",
+ "Doc": "It is not possible to use (*time.Timer).Reset's return value correctly\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1025",
+ "Default": false
+ },
+ {
+ "Name": "SA1026",
+ "Doc": "Cannot marshal channels or functions\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1026",
+ "Default": false
+ },
+ {
+ "Name": "SA1027",
+ "Doc": "Atomic access to 64-bit variable must be 64-bit aligned\n\nOn ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1027",
+ "Default": false
+ },
+ {
+ "Name": "SA1028",
+ "Doc": "sort.Slice can only be used on slices\n\nThe first argument of sort.Slice must be a slice.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1028",
+ "Default": false
+ },
+ {
+ "Name": "SA1029",
+ "Doc": "Inappropriate key in call to context.WithValue\n\nThe provided key must be comparable and should not be\nof type string or any other built-in type to avoid collisions between\npackages using context. Users of WithValue should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an interface{},\ncontext keys often have concrete type struct{}. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1029",
+ "Default": false
+ },
+ {
+ "Name": "SA1030",
+ "Doc": "Invalid argument in call to a strconv function\n\nThis check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in strconv.\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1030",
+ "Default": false
+ },
+ {
+ "Name": "SA1031",
+ "Doc": "Overlapping byte slices passed to an encoder\n\nIn an encoding function of the form Encode(dst, src), dst and\nsrc were found to reference the same memory. This can result in\nsrc bytes being overwritten before they are read, when the encoder\nwrites more than one byte per src byte.\n\nAvailable since\n 2024.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1031",
+ "Default": false
+ },
+ {
+ "Name": "SA1032",
+ "Doc": "Wrong order of arguments to errors.Is\n\nThe first argument of the function errors.Is is the error\nthat we have and the second argument is the error we're trying to match against.\nFor example:\n\n\tif errors.Is(err, io.EOF) { ... }\n\nThis check detects some cases where the two arguments have been swapped. It\nflags any calls where the first argument is referring to a package-level error\nvariable, such as\n\n\tif errors.Is(io.EOF, err) { /* this is wrong */ }\n\nAvailable since\n 2024.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA1032",
+ "Default": false
+ },
+ {
+ "Name": "SA2001",
+ "Doc": "Empty critical section, did you mean to defer the unlock?\n\nEmpty critical sections of the kind\n\n mu.Lock()\n mu.Unlock()\n\nare very often a typo, and the following was intended instead:\n\n mu.Lock()\n defer mu.Unlock()\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a //lint:ignore directive can be used to suppress this\nrare false positive.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA2001",
+ "Default": true
+ },
+ {
+ "Name": "SA2002",
+ "Doc": "Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA2002",
+ "Default": false
+ },
+ {
+ "Name": "SA2003",
+ "Doc": "Deferred Lock right after locking, likely meant to defer Unlock instead\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA2003",
+ "Default": false
+ },
+ {
+ "Name": "SA3000",
+ "Doc": "TestMain doesn't call os.Exit, hiding test failures\n\nTest executables (and in turn 'go test') exit with a non-zero status\ncode if any tests failed. When specifying your own TestMain function,\nit is your responsibility to arrange for this, by calling os.Exit with\nthe correct code. The correct code is returned by (*testing.M).Run, so\nthe usual way of implementing TestMain is to end it with\nos.Exit(m.Run()).\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA3000",
+ "Default": true
+ },
+ {
+ "Name": "SA3001",
+ "Doc": "Assigning to b.N in benchmarks distorts the results\n\nThe testing package dynamically sets b.N to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter b.N as this would\nfalsify results.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA3001",
+ "Default": true
+ },
+ {
+ "Name": "SA4000",
+ "Doc": "Binary operator has identical expressions on both sides\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4000",
+ "Default": true
+ },
+ {
+ "Name": "SA4001",
+ "Doc": "\u0026*x gets simplified to x, it does not copy x\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4001",
+ "Default": true
},
{
- "FileType": "Go",
- "Lens": "regenerate_cgo",
- "Title": "Re-generate cgo declarations",
- "Doc": "\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
- "Default": true,
- "Status": ""
+ "Name": "SA4003",
+ "Doc": "Comparing unsigned values against negative values is pointless\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4003",
+ "Default": true
},
{
- "FileType": "Go",
- "Lens": "test",
- "Title": "Run tests and benchmarks",
- "Doc": "\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
- "Default": false,
- "Status": ""
+ "Name": "SA4004",
+ "Doc": "The loop exits unconditionally after one iteration\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4004",
+ "Default": true
},
{
- "FileType": "go.mod",
- "Lens": "run_govulncheck",
- "Title": "Run govulncheck (legacy)",
- "Doc": "\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
- "Default": false,
- "Status": "experimental"
+ "Name": "SA4005",
+ "Doc": "Field assignment that will never be observed. Did you mean to use a pointer receiver?\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4005",
+ "Default": false
},
{
- "FileType": "go.mod",
- "Lens": "tidy",
- "Title": "Tidy go.mod file",
- "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
- "Default": true,
- "Status": ""
+ "Name": "SA4006",
+ "Doc": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4006",
+ "Default": false
},
{
- "FileType": "go.mod",
- "Lens": "upgrade_dependency",
- "Title": "Update dependencies",
- "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
- "Default": true,
- "Status": ""
+ "Name": "SA4008",
+ "Doc": "The variable in the loop condition never changes, are you incrementing the wrong variable?\n\nFor example:\n\n\tfor i := 0; i \u003c 10; j++ { ... }\n\nThis may also occur when a loop can only execute once because of unconditional\ncontrol flow that terminates the loop. For example, when a loop body contains an\nunconditional break, return, or panic:\n\n\tfunc f() {\n\t\tpanic(\"oops\")\n\t}\n\tfunc g() {\n\t\tfor i := 0; i \u003c 10; i++ {\n\t\t\t// f unconditionally calls panic, which means \"i\" is\n\t\t\t// never incremented.\n\t\t\tf()\n\t\t}\n\t}\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4008",
+ "Default": false
},
{
- "FileType": "go.mod",
- "Lens": "vendor",
- "Title": "Update vendor directory",
- "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
- "Default": true,
- "Status": ""
+ "Name": "SA4009",
+ "Doc": "A function argument is overwritten before its first use\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4009",
+ "Default": false
},
{
- "FileType": "go.mod",
- "Lens": "vulncheck",
- "Title": "Run govulncheck",
- "Doc": "\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
- "Default": false,
- "Status": "experimental"
- }
- ],
- "Analyzers": [
+ "Name": "SA4010",
+ "Doc": "The result of append will never be observed anywhere\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4010",
+ "Default": false
+ },
+ {
+ "Name": "SA4011",
+ "Doc": "Break statement with no effect. Did you mean to break out of an outer loop?\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4011",
+ "Default": true
+ },
+ {
+ "Name": "SA4012",
+ "Doc": "Comparing a value against NaN even though no value is equal to NaN\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4012",
+ "Default": false
+ },
+ {
+ "Name": "SA4013",
+ "Doc": "Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4013",
+ "Default": true
+ },
+ {
+ "Name": "SA4014",
+ "Doc": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4014",
+ "Default": true
+ },
+ {
+ "Name": "SA4015",
+ "Doc": "Calling functions like math.Ceil on floats converted from integers doesn't do anything useful\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4015",
+ "Default": false
+ },
+ {
+ "Name": "SA4016",
+ "Doc": "Certain bitwise operations, such as x ^ 0, do not do anything useful\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4016",
+ "Default": true
+ },
+ {
+ "Name": "SA4017",
+ "Doc": "Discarding the return values of a function without side effects, making the call pointless\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4017",
+ "Default": false
+ },
+ {
+ "Name": "SA4018",
+ "Doc": "Self-assignment of variables\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4018",
+ "Default": false
+ },
+ {
+ "Name": "SA4019",
+ "Doc": "Multiple, identical build constraints in the same file\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4019",
+ "Default": true
+ },
+ {
+ "Name": "SA4020",
+ "Doc": "Unreachable case clause in a type switch\n\nIn a type switch like the following\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n\n var v interface{} = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case T:\n // unreachable\n }\n\nthe second case clause can never be reached because T implements\nio.Reader and case clauses are evaluated in source order.\n\nAnother example:\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n func (T) Close() error { return nil }\n\n var v interface{} = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case io.ReadCloser:\n // unreachable\n }\n\nEven though T has a Close method and thus implements io.ReadCloser,\nio.Reader will always match first. The method set of io.Reader is a\nsubset of io.ReadCloser. Thus it is impossible to match the second\ncase without matching the first case.\n\n\nStructurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n type T error\n type V error\n\n func doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n }\n\nthe following type switch will have an unreachable case clause:\n\n switch doSomething().(type) {\n case T:\n // ...\n case V:\n // unreachable\n }\n\nT will always match before V because they are structurally equivalent\nand therefore doSomething()'s return value implements both.\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4020",
+ "Default": true
+ },
+ {
+ "Name": "SA4022",
+ "Doc": "Comparing the address of a variable against nil\n\nCode such as 'if \u0026x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4022",
+ "Default": true
+ },
+ {
+ "Name": "SA4023",
+ "Doc": "Impossible comparison of interface value with untyped nil\n\nUnder the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n func returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n }\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n func returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n }\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as *MyError, to help guarantee the error is\ncreated correctly. As an example, os.Open returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection at https://golang.org/doc/articles/laws_of_reflection.html.\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.\n\nAvailable since\n 2020.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4023",
+ "Default": false
+ },
+ {
+ "Name": "SA4024",
+ "Doc": "Checking for impossible return value from a builtin function\n\nReturn values of the len and cap builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n if len(slice) \u003c 0 {\n fmt.Println(\"unreachable code\")\n }\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4024",
+ "Default": true
+ },
+ {
+ "Name": "SA4025",
+ "Doc": "Integer division of literals that results in zero\n\nWhen dividing two integer constants, the result will\nalso be an integer. Thus, a division such as 2 / 3 results in 0.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4025",
+ "Default": true
+ },
+ {
+ "Name": "SA4026",
+ "Doc": "Go constants cannot express negative zero\n\nIn IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals -0.0 and 0.0 have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\nmath.Copysign function: math.Copysign(0, -1).\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4026",
+ "Default": true
+ },
+ {
+ "Name": "SA4027",
+ "Doc": "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL\n\n(*net/url.URL).Query parses the current value of net/url.URL.RawQuery\nand returns it as a map of type net/url.Values. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's RawQuery.\n\nAs a consequence, the following code pattern is an expensive no-op:\nu.Query().Add(key, value).\n\nAvailable since\n 2021.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4027",
+ "Default": true
+ },
+ {
+ "Name": "SA4028",
+ "Doc": "x % 1 is always zero\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4028",
+ "Default": true
+ },
+ {
+ "Name": "SA4029",
+ "Doc": "Ineffective attempt at sorting slice\n\nsort.Float64Slice, sort.IntSlice, and sort.StringSlice are\ntypes, not functions. Doing x = sort.StringSlice(x) does nothing,\nespecially not sort any values. The correct usage is\nsort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),\nbut there are more convenient helpers, namely sort.Float64s,\nsort.Ints, and sort.Strings.\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4029",
+ "Default": true
+ },
+ {
+ "Name": "SA4030",
+ "Doc": "Ineffective attempt at generating random number\n\nFunctions in the math/rand package that accept upper limits, such\nas Intn, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be \u003e= 0 and \u003c n – they\ndon't include n. rand.Intn(1) therefore doesn't generate 0\nor 1, it always generates 0.\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4030",
+ "Default": true
+ },
+ {
+ "Name": "SA4031",
+ "Doc": "Checking never-nil value against nil\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4031",
+ "Default": false
+ },
+ {
+ "Name": "SA4032",
+ "Doc": "Comparing runtime.GOOS or runtime.GOARCH against impossible value\n\nAvailable since\n 2024.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA4032",
+ "Default": true
+ },
+ {
+ "Name": "SA5000",
+ "Doc": "Assignment to nil map\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5000",
+ "Default": false
+ },
+ {
+ "Name": "SA5001",
+ "Doc": "Deferring Close before checking for a possible error\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5001",
+ "Default": true
+ },
+ {
+ "Name": "SA5002",
+ "Doc": "The empty for loop ('for {}') spins and can block the scheduler\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5002",
+ "Default": false
+ },
+ {
+ "Name": "SA5003",
+ "Doc": "Defers in infinite loops will never execute\n\nDefers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5003",
+ "Default": true
+ },
+ {
+ "Name": "SA5004",
+ "Doc": "'for { select { ...' with an empty default branch spins\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5004",
+ "Default": true
+ },
+ {
+ "Name": "SA5005",
+ "Doc": "The finalizer references the finalized object, preventing garbage collection\n\nA finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5005",
+ "Default": false
+ },
+ {
+ "Name": "SA5007",
+ "Doc": "Infinite recursive call\n\nA function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5007",
+ "Default": false
+ },
+ {
+ "Name": "SA5008",
+ "Doc": "Invalid struct tag\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5008",
+ "Default": true
+ },
+ {
+ "Name": "SA5010",
+ "Doc": "Impossible type assertion\n\nSome type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5010",
+ "Default": false
+ },
+ {
+ "Name": "SA5011",
+ "Doc": "Possible nil pointer dereference\n\nA pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n func fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n }\n\n func TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n }\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to panic or log.Fatal. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n func Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n }\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n }\n\n func fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n }\n\nStaticcheck will flag the dereference of x, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n }\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5011",
+ "Default": false
+ },
+ {
+ "Name": "SA5012",
+ "Doc": "Passing odd-sized slice to function expecting even size\n\nSome functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, strings.NewReplacer takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.\n\nAvailable since\n 2020.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA5012",
+ "Default": false
+ },
+ {
+ "Name": "SA6000",
+ "Doc": "Using regexp.Match or related in a loop, should use regexp.Compile\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA6000",
+ "Default": false
+ },
+ {
+ "Name": "SA6001",
+ "Doc": "Missing an optimization opportunity when indexing maps by byte slices\n\nMap keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes m[string(b)] and\nuses the data of b directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n k := string(b)\n println(m[k])\n println(m[k])\n\nwill be less efficient than\n\n println(m[string(b)])\n println(m[string(b)])\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA6001",
+ "Default": false
+ },
+ {
+ "Name": "SA6002",
+ "Doc": "Storing non-pointer values in sync.Pool allocates memory\n\nA sync.Pool is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA6002",
+ "Default": false
+ },
+ {
+ "Name": "SA6003",
+ "Doc": "Converting a string to a slice of runes before ranging over it\n\nYou may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n for _, r := range s {}\n\nand\n\n for _, r := range []rune(s) {}\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA6003",
+ "Default": false
+ },
+ {
+ "Name": "SA6005",
+ "Doc": "Inefficient string comparison with strings.ToLower or strings.ToUpper\n\nConverting two strings to the same case and comparing them like so\n\n if strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n }\n\nis significantly more expensive than comparing them with\nstrings.EqualFold(s1, s2). This is due to memory usage as well as\ncomputational complexity.\n\nstrings.ToLower will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA6005",
+ "Default": true
+ },
+ {
+ "Name": "SA6006",
+ "Doc": "Using io.WriteString to write []byte\n\nUsing io.WriteString to write a slice of bytes, as in\n\n io.WriteString(w, string(b))\n\nis both unnecessary and inefficient. Converting from []byte to string\nhas to allocate and copy the data, and we could simply use w.Write(b)\ninstead.\n\nAvailable since\n 2024.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA6006",
+ "Default": true
+ },
+ {
+ "Name": "SA9001",
+ "Doc": "Defers in range loops may not run when you expect them to\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9001",
+ "Default": false
+ },
+ {
+ "Name": "SA9002",
+ "Doc": "Using a non-octal os.FileMode that looks like it was meant to be in octal.\n\nAvailable since\n 2017.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9002",
+ "Default": true
+ },
+ {
+ "Name": "SA9003",
+ "Doc": "Empty body in an if or else branch\n\nAvailable since\n 2017.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9003",
+ "Default": false
+ },
+ {
+ "Name": "SA9004",
+ "Doc": "Only the first constant has an explicit type\n\nIn a constant declaration such as the following:\n\n const (\n First byte = 1\n Second = 2\n )\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n const (\n First byte = iota\n Second\n )\n\nwhere First and Second do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n const (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n )\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\nWrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n package pkg\n\n const (\n EnumFirst uint8 = 1\n EnumSecond = 2\n )\n\n func fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n }\n\nfails to compile with\n\n ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n\n\nLosing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n package main\n\n import \"fmt\"\n\n type Enum int\n\n func (e Enum) String() string {\n return \"an enum\"\n }\n\n const (\n EnumFirst Enum = 1\n EnumSecond = 2\n )\n\n func main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n }\n\nThis code will output\n\n an enum\n 2\n\nas EnumSecond has no explicit type, and thus defaults to int.\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9004",
+ "Default": true
+ },
+ {
+ "Name": "SA9005",
+ "Doc": "Trying to marshal a struct with no public fields nor custom marshaling\n\nThe encoding/json and encoding/xml packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via MarshalJSON methods. It will also not\nflag empty structs.\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9005",
+ "Default": false
+ },
+ {
+ "Name": "SA9006",
+ "Doc": "Dubious bit shifting of a fixed size integer value\n\nBit shifting a value past its size will always clear the value.\n\nFor instance:\n\n v := int8(42)\n v \u003e\u003e= 8\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n // Clear any value above 32 bits if integers are more than 32 bits.\n func f(i int) int {\n v := i \u003e\u003e 32\n v = v \u003c\u003c 32\n return i-v\n }\n\nAvailable since\n 2020.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9006",
+ "Default": true
+ },
+ {
+ "Name": "SA9007",
+ "Doc": "Deleting a directory that shouldn't be deleted\n\nIt is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakenly using os.TempDir instead\nof ioutil.TempDir, or by forgetting to add a suffix to the result\nof os.UserHomeDir.\n\nWriting\n\n d := os.TempDir()\n defer os.RemoveAll(d)\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9007",
+ "Default": false
+ },
+ {
+ "Name": "SA9008",
+ "Doc": "else branch of a type assertion is probably not reading the right value\n\nWhen declaring variables as part of an if statement (like in 'if\nfoo := ...; foo {'), the same variables will also be in the scope of\nthe else branch. This means that in the following example\n\n if x, ok := x.(int); ok {\n // ...\n } else {\n fmt.Printf(\"unexpected type %T\", x)\n }\n\nx in the else branch will refer to the x from x, ok\n:=; it will not refer to the x that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so x in the else branch will always have the\nvalue 0 and the type int.\n\nAvailable since\n 2022.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9008",
+ "Default": false
+ },
+ {
+ "Name": "SA9009",
+ "Doc": "Ineffectual Go compiler directive\n\nA potential Go compiler directive was found, but is ineffectual as it begins\nwith whitespace.\n\nAvailable since\n 2024.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#SA9009",
+ "Default": true
+ },
+ {
+ "Name": "ST1000",
+ "Doc": "Incorrect or missing package comment\n\nPackages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://go.dev/wiki/CodeReviewComments#package-comments.\n\nAvailable since\n 2019.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1000",
+ "Default": false
+ },
+ {
+ "Name": "ST1001",
+ "Doc": "Dot imports are discouraged\n\nDot imports that aren't in external test packages are discouraged.\n\nThe dot_import_whitelist option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n\u003e The import . form can be useful in tests that, due to circular\n\u003e dependencies, cannot be made part of the package being tested:\n\u003e \n\u003e package foo_test\n\u003e \n\u003e import (\n\u003e \"bar/testutil\" // also imports \"foo\"\n\u003e . \"foo\"\n\u003e )\n\u003e \n\u003e In this case, the test file cannot be in package foo because it\n\u003e uses bar/testutil, which imports foo. So we use the import .\n\u003e form to let the file pretend to be part of package foo even though\n\u003e it is not. Except for this one case, do not use import . in your\n\u003e programs. It makes the programs much harder to read because it is\n\u003e unclear whether a name like Quux is a top-level identifier in the\n\u003e current package or in an imported package.\n\nAvailable since\n 2019.1\n\nOptions\n dot_import_whitelist\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1001",
+ "Default": false
+ },
+ {
+ "Name": "ST1003",
+ "Doc": "Poorly chosen identifier\n\nIdentifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://go.dev/doc/effective_go#package-names\n- https://go.dev/doc/effective_go#mixed-caps\n- https://go.dev/wiki/CodeReviewComments#initialisms\n- https://go.dev/wiki/CodeReviewComments#variable-names\n\nAvailable since\n 2019.1, non-default\n\nOptions\n initialisms\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1003",
+ "Default": false
+ },
+ {
+ "Name": "ST1005",
+ "Doc": "Incorrectly formatted error string\n\nError strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n\u003e Error strings should not be capitalized (unless beginning with\n\u003e proper nouns or acronyms) or end with punctuation, since they are\n\u003e usually printed following other context. That is, use\n\u003e fmt.Errorf(\"something bad\") not fmt.Errorf(\"Something bad\"), so\n\u003e that log.Printf(\"Reading %s: %v\", filename, err) formats without a\n\u003e spurious capital letter mid-message.\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1005",
+ "Default": false
+ },
+ {
+ "Name": "ST1006",
+ "Doc": "Poorly chosen receiver name\n\nQuoting Go Code Review Comments:\n\n\u003e The name of a method's receiver should be a reflection of its\n\u003e identity; often a one or two letter abbreviation of its type\n\u003e suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n\u003e names such as \"me\", \"this\" or \"self\", identifiers typical of\n\u003e object-oriented languages that place more emphasis on methods as\n\u003e opposed to functions. The name need not be as descriptive as that\n\u003e of a method argument, as its role is obvious and serves no\n\u003e documentary purpose. It can be very short as it will appear on\n\u003e almost every line of every method of the type; familiarity admits\n\u003e brevity. Be consistent, too: if you call the receiver \"c\" in one\n\u003e method, don't call it \"cl\" in another.\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1006",
+ "Default": false
+ },
+ {
+ "Name": "ST1008",
+ "Doc": "A function's error value should be its last return value\n\nA function's error value should be its last return value.\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1008",
+ "Default": false
+ },
+ {
+ "Name": "ST1011",
+ "Doc": "Poorly chosen name for variable of type time.Duration\n\ntime.Duration values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like 5 * time.Microsecond\nyields the value 5000. It is therefore not appropriate to suffix a\nvariable of type time.Duration with any time unit, such as Msec or\nMilli.\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1011",
+ "Default": false
+ },
+ {
+ "Name": "ST1012",
+ "Doc": "Poorly chosen name for error variable\n\nError variables that are part of an API should be called errFoo or\nErrFoo.\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1012",
+ "Default": false
+ },
+ {
+ "Name": "ST1013",
+ "Doc": "Should use constants for HTTP error codes, not magic numbers\n\nHTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The net/http\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.\n\nAvailable since\n 2019.1\n\nOptions\n http_status_code_whitelist\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1013",
+ "Default": false
+ },
+ {
+ "Name": "ST1015",
+ "Doc": "A switch's default case should be the first or last case\n\nAvailable since\n 2019.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1015",
+ "Default": false
+ },
+ {
+ "Name": "ST1016",
+ "Doc": "Use consistent method receiver names\n\nAvailable since\n 2019.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1016",
+ "Default": false
+ },
+ {
+ "Name": "ST1017",
+ "Doc": "Don't use Yoda conditions\n\nYoda conditions are conditions of the kind 'if 42 == x', where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic 'if x == 42'.\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1017",
+ "Default": false
+ },
+ {
+ "Name": "ST1018",
+ "Doc": "Avoid zero-width and control characters in string literals\n\nAvailable since\n 2019.2\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1018",
+ "Default": false
+ },
+ {
+ "Name": "ST1019",
+ "Doc": "Importing the same package multiple times\n\nGo allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n import (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n )\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.\n\nAvailable since\n 2020.1\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1019",
+ "Default": false
+ },
+ {
+ "Name": "ST1020",
+ "Doc": "The documentation of an exported function should start with the function's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1020",
+ "Default": false
+ },
+ {
+ "Name": "ST1021",
+ "Doc": "The documentation of an exported type should start with type's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1021",
+ "Default": false
+ },
+ {
+ "Name": "ST1022",
+ "Doc": "The documentation of an exported variable or constant should start with variable's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#ST1022",
+ "Default": false
+ },
+ {
+ "Name": "ST1023",
+ "Doc": "Redundant type in variable declaration\n\nAvailable since\n 2021.1, non-default\n",
+ "URL": "https://staticcheck.dev/docs/checks/#",
+ "Default": false
+ },
{
"Name": "appends",
"Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.",
@@ -1303,7 +3177,7 @@
{
"Name": "hostport",
"Doc": "check format of addresses passed to net.Dial\n\nThis analyzer flags code that produce network address strings using\nfmt.Sprintf, as in this example:\n\n addr := fmt.Sprintf(\"%s:%d\", host, 12345) // \"will not work with IPv6\"\n ...\n conn, err := net.Dial(\"tcp\", addr) // \"when passed to dial here\"\n\nThe analyzer suggests a fix to use the correct approach, a call to\nnet.JoinHostPort:\n\n addr := net.JoinHostPort(host, \"12345\")\n ...\n conn, err := net.Dial(\"tcp\", addr)\n\nA similar diagnostic and fix are produced for a format string of \"%s:%s\".\n",
- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/hostport",
+ "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/hostport",
"Default": true
},
{
@@ -1338,7 +3212,7 @@
},
{
"Name": "modernize",
- "Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go and its standard\nlibrary.\n\nEach diagnostic provides a fix. Our intent is that these fixes may\nbe safely applied en masse without changing the behavior of your\nprogram. In some cases the suggested fixes are imperfect and may\nlead to (for example) unused imports or unused local variables,\ncausing build breakage. However, these problems are generally\ntrivial to fix. We regard any modernizer whose fix changes program\nbehavior to have a serious bug and will endeavor to fix it.\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.\n\nChanges produced by this tool should be reviewed as usual before\nbeing merged. In some cases, a loop may be replaced by a simple\nfunction call, causing comments within the loop to be discarded.\nHuman judgment may be required to avoid losing comments of value.\n\nEach diagnostic reported by modernize has a specific category. (The\ncategories are listed below.) Diagnostics in some categories, such\nas \"efaceany\" (which replaces \"interface{}\" with \"any\" where it is\nsafe to do so) are particularly numerous. It may ease the burden of\ncode review to apply fixes in two passes, the first change\nconsisting only of fixes of category \"efaceany\", the second\nconsisting of all others. This can be achieved using the -category flag:\n\n\t$ modernize -category=efaceany -fix -test ./...\n\t$ modernize -category=-efaceany -fix -test ./...\n\nCategories of modernize diagnostic:\n\n - forvar: remove x := x variable declarations made unnecessary by the new semantics of loops in go1.22.\n\n - slicescontains: replace 'for i, elem := range s { if elem == needle { ...; break }'\n by a call to slices.Contains, added in go1.21.\n\n - minmax: replace an if/else conditional assignment by a call to\n the built-in min or max functions added in go1.21.\n\n - sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21.\n\n - efaceany: replace interface{} by the 'any' type added in go1.18.\n\n - slicesclone: replace append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21.\n\n - mapsloop: replace a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions from\n the maps package, added in go1.21.\n\n - fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19.\n\n - testingcontext: replace uses of context.WithCancel in tests\n with t.Context, added in go1.24.\n\n - omitzero: replace omitempty by omitzero on structs, added in go1.24.\n\n - bloop: replace \"for i := range b.N\" or \"for range b.N\" in a\n benchmark with \"for b.Loop()\", and remove any preceding calls\n to b.StopTimer, b.StartTimer, and b.ResetTimer.\n\n - slicesdelete: replace append(s[:i], s[i+1]...) by\n slices.Delete(s, i, i+1), added in go1.21.\n\n - rangeint: replace a 3-clause \"for i := 0; i \u003c n; i++\" loop by\n \"for i := range n\", added in go1.22.\n\n - stringsseq: replace Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq.\n\n - stringscutprefix: replace some uses of HasPrefix followed by TrimPrefix with CutPrefix,\n added to the strings package in go1.20.\n\n - waitgroup: replace old complex usages of sync.WaitGroup by less complex WaitGroup.Go method in go1.25.",
+ "Doc": "simplify code by using modern constructs\n\nThis analyzer reports opportunities for simplifying and clarifying\nexisting code by using more modern features of Go and its standard\nlibrary.\n\nEach diagnostic provides a fix. Our intent is that these fixes may\nbe safely applied en masse without changing the behavior of your\nprogram. In some cases the suggested fixes are imperfect and may\nlead to (for example) unused imports or unused local variables,\ncausing build breakage. However, these problems are generally\ntrivial to fix. We regard any modernizer whose fix changes program\nbehavior to have a serious bug and will endeavor to fix it.\n\nTo apply all modernization fixes en masse, you can use the\nfollowing command:\n\n\t$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...\n\n(Do not use \"go get -tool\" to add gopls as a dependency of your\nmodule; gopls commands must be built from their release branch.)\n\nIf the tool warns of conflicting fixes, you may need to run it more\nthan once until it has applied all fixes cleanly. This command is\nnot an officially supported interface and may change in the future.\n\nChanges produced by this tool should be reviewed as usual before\nbeing merged. In some cases, a loop may be replaced by a simple\nfunction call, causing comments within the loop to be discarded.\nHuman judgment may be required to avoid losing comments of value.\n\nEach diagnostic reported by modernize has a specific category. (The\ncategories are listed below.) Diagnostics in some categories, such\nas \"efaceany\" (which replaces \"interface{}\" with \"any\" where it is\nsafe to do so) are particularly numerous. It may ease the burden of\ncode review to apply fixes in two passes, the first change\nconsisting only of fixes of category \"efaceany\", the second\nconsisting of all others. This can be achieved using the -category flag:\n\n\t$ modernize -category=efaceany -fix -test ./...\n\t$ modernize -category=-efaceany -fix -test ./...\n\nCategories of modernize diagnostic:\n\n - forvar: remove x := x variable declarations made unnecessary by the new semantics of loops in go1.22.\n\n - slicescontains: replace 'for i, elem := range s { if elem == needle { ...; break }'\n by a call to slices.Contains, added in go1.21.\n\n - minmax: replace an if/else conditional assignment by a call to\n the built-in min or max functions added in go1.21.\n\n - sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] \u003c s[j] }\n by a call to slices.Sort(s), added in go1.21.\n\n - efaceany: replace interface{} by the 'any' type added in go1.18.\n\n - slicesclone: replace append([]T(nil), s...) by slices.Clone(s) or\n slices.Concat(s), added in go1.21.\n\n - mapsloop: replace a loop around an m[k]=v map update by a call\n to one of the Collect, Copy, Clone, or Insert functions from\n the maps package, added in go1.21.\n\n - fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),\n added in go1.19.\n\n - testingcontext: replace uses of context.WithCancel in tests\n with t.Context, added in go1.24.\n\n - omitzero: replace omitempty by omitzero on structs, added in go1.24.\n\n - bloop: replace \"for i := range b.N\" or \"for range b.N\" in a\n benchmark with \"for b.Loop()\", and remove any preceding calls\n to b.StopTimer, b.StartTimer, and b.ResetTimer.\n\n - slicesdelete: replace append(s[:i], s[i+1]...) by\n slices.Delete(s, i, i+1), added in go1.21.\n\n - rangeint: replace a 3-clause \"for i := 0; i \u003c n; i++\" loop by\n \"for i := range n\", added in go1.22.\n\n - stringsseq: replace Split in \"for range strings.Split(...)\" by go1.24's\n more efficient SplitSeq, or Fields with FieldSeq.\n\n - stringscutprefix: replace some uses of HasPrefix followed by TrimPrefix with CutPrefix,\n added to the strings package in go1.20.\n\n - waitgroup: replace old complex usages of sync.WaitGroup by less complex WaitGroup.Go method in go1.25.",
"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize",
"Default": true
},
diff --git a/gopls/internal/doc/generate/generate.go b/gopls/internal/doc/generate/generate.go
index 762fceeb4b9..d470fb71333 100644
--- a/gopls/internal/doc/generate/generate.go
+++ b/gopls/internal/doc/generate/generate.go
@@ -136,7 +136,7 @@ func loadAPI() (*doc.API, error) {
defaults := settings.DefaultOptions()
api := &doc.API{
Options: map[string][]*doc.Option{},
- Analyzers: loadAnalyzers(settings.DefaultAnalyzers), // no staticcheck analyzers
+ Analyzers: loadAnalyzers(settings.AllAnalyzers, defaults),
}
api.Lenses, err = loadLenses(settingsPkg, defaults.Codelenses)
@@ -505,20 +505,17 @@ func loadLenses(settingsPkg *packages.Package, defaults map[settings.CodeLensSou
return lenses, nil
}
-func loadAnalyzers(m map[string]*settings.Analyzer) []*doc.Analyzer {
- var sorted []string
- for _, a := range m {
- sorted = append(sorted, a.Analyzer().Name)
- }
- sort.Strings(sorted)
+func loadAnalyzers(analyzers []*settings.Analyzer, defaults *settings.Options) []*doc.Analyzer {
+ slices.SortFunc(analyzers, func(x, y *settings.Analyzer) int {
+ return strings.Compare(x.Analyzer().Name, y.Analyzer().Name)
+ })
var json []*doc.Analyzer
- for _, name := range sorted {
- a := m[name]
+ for _, a := range analyzers {
json = append(json, &doc.Analyzer{
Name: a.Analyzer().Name,
Doc: a.Analyzer().Doc,
URL: a.Analyzer().URL,
- Default: a.EnabledByDefault(),
+ Default: a.Enabled(defaults),
})
}
return json
@@ -805,7 +802,7 @@ func replaceSection(content []byte, sectionName string, replacement []byte) ([]b
if idx == nil {
return nil, fmt.Errorf("could not find section %q", sectionName)
}
- result := append([]byte(nil), content[:idx[2]]...)
+ result := slices.Clone(content[:idx[2]])
result = append(result, replacement...)
result = append(result, content[idx[3]:]...)
return result, nil
diff --git a/gopls/internal/filecache/filecache_test.go b/gopls/internal/filecache/filecache_test.go
index 3419db4b513..4dbc04490f5 100644
--- a/gopls/internal/filecache/filecache_test.go
+++ b/gopls/internal/filecache/filecache_test.go
@@ -100,7 +100,6 @@ func TestConcurrency(t *testing.T) {
// there is no third possibility.
var group errgroup.Group
for i := range values {
- i := i
group.Go(func() error { return filecache.Set(kind, key, values[i][:]) })
group.Go(func() error { return get(false) })
}
@@ -217,12 +216,12 @@ func BenchmarkUncontendedGet(b *testing.B) {
if err := filecache.Set(kind, key, value[:]); err != nil {
b.Fatal(err)
}
- b.ResetTimer()
+
b.SetBytes(int64(len(value)))
var group errgroup.Group
group.SetLimit(50)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
group.Go(func() error {
_, err := filecache.Get(kind, key)
return err
@@ -246,7 +245,7 @@ func BenchmarkUncontendedSet(b *testing.B) {
const P = 1000 // parallelism
b.SetBytes(P * int64(len(value)))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
// Perform P concurrent calls to Set. All must succeed.
var group errgroup.Group
for range [P]bool{} {
diff --git a/gopls/internal/golang/addtest.go b/gopls/internal/golang/addtest.go
index e952874e109..3a5b1e03308 100644
--- a/gopls/internal/golang/addtest.go
+++ b/gopls/internal/golang/addtest.go
@@ -480,8 +480,6 @@ func AddTestForFunc(ctx context.Context, snapshot *cache.Snapshot, loc protocol.
},
}
- errorType := types.Universe.Lookup("error").Type()
-
var isContextType = func(t types.Type) bool {
named, ok := t.(*types.Named)
if !ok {
diff --git a/gopls/internal/golang/call_hierarchy.go b/gopls/internal/golang/call_hierarchy.go
index 04dc9deeb5d..b9f21cd18d7 100644
--- a/gopls/internal/golang/call_hierarchy.go
+++ b/gopls/internal/golang/call_hierarchy.go
@@ -14,13 +14,16 @@ import (
"path/filepath"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/cache/parsego"
"golang.org/x/tools/gopls/internal/file"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/util/bug"
+ "golang.org/x/tools/gopls/internal/util/moremaps"
"golang.org/x/tools/gopls/internal/util/safetoken"
"golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/typesinternal"
)
// PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file.
@@ -99,7 +102,7 @@ func IncomingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle
// Flatten the map of pointers into a slice of values.
incomingCallItems := make([]protocol.CallHierarchyIncomingCall, 0, len(incomingCalls))
- for _, callItem := range incomingCalls {
+ for _, callItem := range moremaps.SortedFunc(incomingCalls, protocol.CompareLocation) {
incomingCallItems = append(incomingCallItems, *callItem)
}
return incomingCallItems, nil
@@ -247,30 +250,21 @@ func OutgoingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle
type callRange struct {
start, end token.Pos
}
- callRanges := []callRange{}
- ast.Inspect(declNode, func(n ast.Node) bool {
- if call, ok := n.(*ast.CallExpr); ok {
- var start, end token.Pos
- switch n := call.Fun.(type) {
- case *ast.SelectorExpr:
- start, end = n.Sel.NamePos, call.Lparen
- case *ast.Ident:
- start, end = n.NamePos, call.Lparen
- case *ast.FuncLit:
- // while we don't add the function literal as an 'outgoing' call
- // we still want to traverse into it
- return true
- default:
- // ignore any other kind of call expressions
- // for ex: direct function literal calls since that's not an 'outgoing' call
- return false
- }
- callRanges = append(callRanges, callRange{start: start, end: end})
+
+ // Find calls to known functions/methods, including interface methods.
+ var callRanges []callRange
+ for n := range ast.Preorder(declNode) {
+ if call, ok := n.(*ast.CallExpr); ok &&
+ is[*types.Func](typeutil.Callee(pkg.TypesInfo(), call)) {
+ id := typesinternal.UsedIdent(pkg.TypesInfo(), call.Fun)
+ callRanges = append(callRanges, callRange{
+ start: id.NamePos,
+ end: call.Lparen,
+ })
}
- return true
- })
+ }
- outgoingCalls := map[token.Pos]*protocol.CallHierarchyOutgoingCall{}
+ outgoingCalls := make(map[protocol.Location]*protocol.CallHierarchyOutgoingCall)
for _, callRange := range callRanges {
_, obj, _ := referencedObject(declPkg, declPGF, callRange.start)
if obj == nil {
@@ -280,12 +274,13 @@ func OutgoingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle
continue // built-ins have no position
}
- outgoingCall, ok := outgoingCalls[obj.Pos()]
+ loc, err := mapPosition(ctx, declPkg.FileSet(), snapshot, obj.Pos(), obj.Pos()+token.Pos(len(obj.Name())))
+ if err != nil {
+ return nil, err
+ }
+
+ outgoingCall, ok := outgoingCalls[loc]
if !ok {
- loc, err := mapPosition(ctx, declPkg.FileSet(), snapshot, obj.Pos(), obj.Pos()+token.Pos(len(obj.Name())))
- if err != nil {
- return nil, err
- }
outgoingCall = &protocol.CallHierarchyOutgoingCall{
To: protocol.CallHierarchyItem{
Name: obj.Name(),
@@ -297,7 +292,7 @@ func OutgoingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle
SelectionRange: loc.Range,
},
}
- outgoingCalls[obj.Pos()] = outgoingCall
+ outgoingCalls[loc] = outgoingCall
}
rng, err := declPGF.PosRange(callRange.start, callRange.end)
@@ -308,7 +303,7 @@ func OutgoingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle
}
outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls))
- for _, callItem := range outgoingCalls {
+ for _, callItem := range moremaps.SortedFunc(outgoingCalls, protocol.CompareLocation) {
outgoingCallItems = append(outgoingCallItems, *callItem)
}
return outgoingCallItems, nil
diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go
index 7949493a896..a7917fbbda4 100644
--- a/gopls/internal/golang/codeaction.go
+++ b/gopls/internal/golang/codeaction.go
@@ -27,6 +27,7 @@ import (
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/protocol/command"
"golang.org/x/tools/gopls/internal/settings"
+ "golang.org/x/tools/internal/astutil/cursor"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/typesinternal"
@@ -105,9 +106,13 @@ func CodeActions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
req.pkg = nil
}
if err := p.fn(ctx, req); err != nil {
- // TODO(adonovan): most errors in code action providers should
- // not block other providers; see https://go.dev/issue/71275.
- return nil, err
+ // An error in one code action producer
+ // should not affect the others.
+ if ctx.Err() != nil {
+ return nil, err
+ }
+ event.Error(ctx, fmt.Sprintf("CodeAction producer %s failed", p.kind), err)
+ continue
}
}
@@ -258,6 +263,8 @@ var codeActionProducers = [...]codeActionProducer{
{kind: settings.RefactorRewriteMoveParamRight, fn: refactorRewriteMoveParamRight, needPkg: true},
{kind: settings.RefactorRewriteSplitLines, fn: refactorRewriteSplitLines, needPkg: true},
{kind: settings.RefactorRewriteEliminateDotImport, fn: refactorRewriteEliminateDotImport, needPkg: true},
+ {kind: settings.RefactorRewriteAddTags, fn: refactorRewriteAddStructTags, needPkg: true},
+ {kind: settings.RefactorRewriteRemoveTags, fn: refactorRewriteRemoveStructTags, needPkg: true},
{kind: settings.GoplsDocFeatures, fn: goplsDocFeatures}, // offer this one last (#72742)
// Note: don't forget to update the allow-list in Server.CodeAction
@@ -713,33 +720,24 @@ func refactorRewriteEliminateDotImport(ctx context.Context, req *codeActionsRequ
// Go through each use of the dot imported package, checking its scope for
// shadowing and calculating an edit to qualify the identifier.
- var stack []ast.Node
- ast.Inspect(req.pgf.File, func(n ast.Node) bool {
- if n == nil {
- stack = stack[:len(stack)-1] // pop
- return false
- }
- stack = append(stack, n) // push
+ for curId := range req.pgf.Cursor.Preorder((*ast.Ident)(nil)) {
+ ident := curId.Node().(*ast.Ident)
- ident, ok := n.(*ast.Ident)
- if !ok {
- return true
- }
// Only keep identifiers that use a symbol from the
// dot imported package.
use := req.pkg.TypesInfo().Uses[ident]
if use == nil || use.Pkg() == nil {
- return true
+ continue
}
if use.Pkg() != imported {
- return true
+ continue
}
// Only qualify unqualified identifiers (due to dot imports).
// All other references to a symbol imported from another package
// are nested within a select expression (pkg.Foo, v.Method, v.Field).
- if is[*ast.SelectorExpr](stack[len(stack)-2]) {
- return true
+ if is[*ast.SelectorExpr](curId.Parent().Node()) {
+ continue
}
// Make sure that the package name will not be shadowed by something else in scope.
@@ -750,24 +748,22 @@ func refactorRewriteEliminateDotImport(ctx context.Context, req *codeActionsRequ
// allowed to go through.
sc := fileScope.Innermost(ident.Pos())
if sc == nil {
- return true
+ continue
}
_, obj := sc.LookupParent(newName, ident.Pos())
if obj != nil {
- return true
+ continue
}
rng, err := req.pgf.PosRange(ident.Pos(), ident.Pos()) // sic, zero-width range before ident
if err != nil {
- return true
+ continue
}
edits = append(edits, protocol.TextEdit{
Range: rng,
NewText: newName + ".",
})
-
- return true
- })
+ }
req.addEditAction("Eliminate dot import", nil, protocol.DocumentChangeEdit(
req.fh,
@@ -817,6 +813,82 @@ func refactorRewriteFillSwitch(ctx context.Context, req *codeActionsRequest) err
return nil
}
+// selectionContainsStructField returns true if the given struct contains a
+// field between start and end pos. If needsTag is true, it only returns true if
+// the struct field found contains a struct tag.
+func selectionContainsStructField(node *ast.StructType, start, end token.Pos, needsTag bool) bool {
+ for _, field := range node.Fields.List {
+ if start <= field.End() && end >= field.Pos() {
+ if !needsTag || field.Tag != nil {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// selectionContainsStruct returns true if there exists a struct containing
+// fields within start and end positions. If removeTags is true, it means the
+// current command is for remove tags rather than add tags, so we only return
+// true if the struct field found contains a struct tag to remove.
+func selectionContainsStruct(cursor cursor.Cursor, start, end token.Pos, removeTags bool) bool {
+ cur, ok := cursor.FindByPos(start, end)
+ if !ok {
+ return false
+ }
+ if _, ok := cur.Node().(*ast.StructType); ok {
+ return true
+ }
+
+ // Handles case where selection is within struct.
+ for c := range cur.Enclosing((*ast.StructType)(nil)) {
+ if selectionContainsStructField(c.Node().(*ast.StructType), start, end, removeTags) {
+ return true
+ }
+ }
+
+ // Handles case where selection contains struct but may contain other nodes, including other structs.
+ for c := range cur.Preorder((*ast.StructType)(nil)) {
+ node := c.Node().(*ast.StructType)
+ // Check that at least one field is located within the selection. If we are removing tags, that field
+ // must also have a struct tag, otherwise we do not provide the code action.
+ if selectionContainsStructField(node, start, end, removeTags) {
+ return true
+ }
+ }
+ return false
+}
+
+// refactorRewriteAddStructTags produces "Add struct tags" code actions.
+// See [server.commandHandler.ModifyTags] for command implementation.
+func refactorRewriteAddStructTags(ctx context.Context, req *codeActionsRequest) error {
+ if selectionContainsStruct(req.pgf.Cursor, req.start, req.end, false) {
+ // TODO(mkalil): Prompt user for modification args once we have dialogue capabilities.
+ cmdAdd := command.NewModifyTagsCommand("Add struct tags", command.ModifyTagsArgs{
+ URI: req.loc.URI,
+ Range: req.loc.Range,
+ Add: "json",
+ })
+ req.addCommandAction(cmdAdd, false)
+ }
+ return nil
+}
+
+// refactorRewriteRemoveStructTags produces "Remove struct tags" code actions.
+// See [server.commandHandler.ModifyTags] for command implementation.
+func refactorRewriteRemoveStructTags(ctx context.Context, req *codeActionsRequest) error {
+ // TODO(mkalil): Prompt user for modification args once we have dialogue capabilities.
+ if selectionContainsStruct(req.pgf.Cursor, req.start, req.end, true) {
+ cmdRemove := command.NewModifyTagsCommand("Remove struct tags", command.ModifyTagsArgs{
+ URI: req.loc.URI,
+ Range: req.loc.Range,
+ Clear: true,
+ })
+ req.addCommandAction(cmdRemove, false)
+ }
+ return nil
+}
+
// removableParameter returns paramInfo about a removable parameter indicated
// by the given [start, end) range, or nil if no such removal is available.
//
@@ -953,7 +1025,7 @@ func goAssembly(ctx context.Context, req *codeActionsRequest) error {
}
sym.WriteString(".")
- curSel, _ := req.pgf.Cursor.FindPos(req.start, req.end)
+ curSel, _ := req.pgf.Cursor.FindByPos(req.start, req.end)
for cur := range curSel.Enclosing((*ast.FuncDecl)(nil), (*ast.ValueSpec)(nil)) {
var name string // in command title
switch node := cur.Node().(type) {
diff --git a/gopls/internal/golang/comment.go b/gopls/internal/golang/comment.go
index 9a360ce2e2b..a58045b1819 100644
--- a/gopls/internal/golang/comment.go
+++ b/gopls/internal/golang/comment.go
@@ -96,11 +96,7 @@ func parseDocLink(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.O
// position of each doc link from the parsed result.
line := safetoken.Line(pgf.Tok, pos)
var start, end token.Pos
- if pgf.Tok.LineStart(line) > comment.Pos() {
- start = pgf.Tok.LineStart(line)
- } else {
- start = comment.Pos()
- }
+ start = max(pgf.Tok.LineStart(line), comment.Pos())
if line < pgf.Tok.LineCount() && pgf.Tok.LineStart(line+1) < comment.End() {
end = pgf.Tok.LineStart(line + 1)
} else {
diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go
index a3270f97909..83be9f2ed80 100644
--- a/gopls/internal/golang/completion/completion.go
+++ b/gopls/internal/golang/completion/completion.go
@@ -489,12 +489,7 @@ type candidate struct {
}
func (c candidate) hasMod(mod typeModKind) bool {
- for _, m := range c.mods {
- if m == mod {
- return true
- }
- }
- return false
+ return slices.Contains(c.mods, mod)
}
// Completion returns a list of possible candidates for completion, given a
@@ -792,7 +787,6 @@ func (c *completer) containingIdent(src []byte) *ast.Ident {
}
fakeIdent := &ast.Ident{Name: lit, NamePos: pos}
-
if _, isBadDecl := c.path[0].(*ast.BadDecl); isBadDecl {
// You don't get *ast.Idents at the file level, so look for bad
// decls and use the manually extracted token.
@@ -807,6 +801,18 @@ func (c *completer) containingIdent(src []byte) *ast.Ident {
// is a keyword. This improves completion after an "accidental
// keyword", e.g. completing to "variance" in "someFunc(var<>)".
return fakeIdent
+ } else if block, ok := c.path[0].(*ast.BlockStmt); ok && len(block.List) != 0 {
+ last := block.List[len(block.List)-1]
+ // Handle incomplete AssignStmt with multiple left-hand vars:
+ // var left, right int
+ // left, ri‸ -> "right"
+ if expr, ok := last.(*ast.ExprStmt); ok &&
+ (is[*ast.Ident](expr.X) ||
+ is[*ast.SelectorExpr](expr.X) ||
+ is[*ast.IndexExpr](expr.X) ||
+ is[*ast.StarExpr](expr.X)) {
+ return fakeIdent
+ }
}
return nil
@@ -1487,7 +1493,6 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
}
for _, uri := range mp.CompiledGoFiles {
- uri := uri
g.Go(func() error {
return quickParse(uri, mp, tooNew)
})
diff --git a/gopls/internal/golang/completion/deep_completion.go b/gopls/internal/golang/completion/deep_completion.go
index 053ece8219e..523c5b8652b 100644
--- a/gopls/internal/golang/completion/deep_completion.go
+++ b/gopls/internal/golang/completion/deep_completion.go
@@ -9,6 +9,8 @@ import (
"go/types"
"strings"
"time"
+
+ "golang.org/x/tools/gopls/internal/util/typesutil"
)
// MaxDeepCompletions limits deep completion results because in most cases
@@ -312,6 +314,9 @@ func deepCandName(cand *candidate) string {
for i, obj := range cand.path {
buf.WriteString(obj.Name())
+ if fn, ok := obj.(*types.Func); ok {
+ buf.WriteString(typesutil.FormatTypeParams(fn.Signature().TypeParams()))
+ }
if cand.pathInvokeMask&(1< 0 {
buf.WriteByte('(')
buf.WriteByte(')')
diff --git a/gopls/internal/golang/completion/deep_completion_test.go b/gopls/internal/golang/completion/deep_completion_test.go
index 27009af1b4f..d522b9be9a9 100644
--- a/gopls/internal/golang/completion/deep_completion_test.go
+++ b/gopls/internal/golang/completion/deep_completion_test.go
@@ -20,7 +20,7 @@ func TestDeepCompletionIsHighScore(t *testing.T) {
}
// Fill up with higher scores.
- for i := 0; i < MaxDeepCompletions; i++ {
+ for range MaxDeepCompletions {
if !s.isHighScore(10) {
t.Error("10 should be high score")
}
diff --git a/gopls/internal/golang/completion/keywords.go b/gopls/internal/golang/completion/keywords.go
index 3f2f5ac78cd..fb1fa1694ce 100644
--- a/gopls/internal/golang/completion/keywords.go
+++ b/gopls/internal/golang/completion/keywords.go
@@ -121,18 +121,69 @@ func (c *completer) addKeywordCompletions() {
c.addKeywordItems(seen, stdScore, BREAK)
}
case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
- c.addKeywordItems(seen, stdScore, CASE, DEFAULT)
+ // if there is no default case yet, it's highly likely to add a default in switch.
+ // we don't offer 'default' anymore if user has used it already in current swtich.
+ if !hasDefaultClause(node) {
+ c.addKeywordItems(seen, highScore, CASE, DEFAULT)
+ }
case *ast.ForStmt, *ast.RangeStmt:
c.addKeywordItems(seen, stdScore, BREAK, CONTINUE)
// This is a bit weak, functions allow for many keywords
case *ast.FuncDecl:
if node.Body != nil && c.pos > node.Body.Lbrace {
- c.addKeywordItems(seen, stdScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
+ // requireReturnObj checks whether user must provide some objects after return.
+ requireReturnObj := func(sig *ast.FuncType) bool {
+ results := sig.Results
+ if results == nil || results.List == nil {
+ return false // nothing to return
+ }
+ // If any result is named, allow a bare return.
+ for _, r := range results.List {
+ for _, name := range r.Names {
+ if name.Name != "_" {
+ return false
+ }
+ }
+ }
+ return true
+ }
+ ret := RETURN
+ if requireReturnObj(node.Type) {
+ // as user must return something, we offer a space after return.
+ // function literal inside a function will be affected by outer function,
+ // but 'go fmt' will help to remove the ending space.
+ // the benefit is greater than introducing an unncessary space.
+ ret += " "
+ }
+
+ c.addKeywordItems(seen, stdScore, DEFER, ret, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
}
}
}
}
+// hasDefaultClause reports whether the given node contains a direct default case.
+// It does not traverse child nodes to look for nested default clauses,
+// and returns false if the node is not a switch statement.
+func hasDefaultClause(node ast.Node) bool {
+ var cases []ast.Stmt
+ switch node := node.(type) {
+ case *ast.TypeSwitchStmt:
+ cases = node.Body.List
+ case *ast.SelectStmt:
+ cases = node.Body.List
+ case *ast.SwitchStmt:
+ cases = node.Body.List
+ }
+ for _, c := range cases {
+ if clause, ok := c.(*ast.CaseClause); ok &&
+ clause.List == nil { // default case
+ return true
+ }
+ }
+ return false
+}
+
// addKeywordItems dedupes and adds completion items for the specified
// keywords with the specified score.
func (c *completer) addKeywordItems(seen map[string]bool, score float64, kws ...string) {
diff --git a/gopls/internal/golang/completion/labels.go b/gopls/internal/golang/completion/labels.go
index f0e5f42a67a..52afafebf25 100644
--- a/gopls/internal/golang/completion/labels.go
+++ b/gopls/internal/golang/completion/labels.go
@@ -8,6 +8,7 @@ import (
"go/ast"
"go/token"
"math"
+ "slices"
)
type labelType int
@@ -96,12 +97,7 @@ func (c *completer) labels(lt labelType) {
// Only search into block-like nodes enclosing our "goto".
// This prevents us from finding labels in nested blocks.
case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause:
- for _, p := range c.path {
- if n == p {
- return true
- }
- }
- return false
+ return slices.Contains(c.path, n)
case *ast.LabeledStmt:
addLabel(highScore, n)
}
diff --git a/gopls/internal/golang/completion/postfix_snippets.go b/gopls/internal/golang/completion/postfix_snippets.go
index 1bafe848490..1d306e3518d 100644
--- a/gopls/internal/golang/completion/postfix_snippets.go
+++ b/gopls/internal/golang/completion/postfix_snippets.go
@@ -334,7 +334,29 @@ if {{$errName | $a.SpecifiedPlaceholder 1}} != nil {
{{end}}
}
{{end}}`,
-}}
+},
+ {
+ label: "tostring",
+ details: "[]byte to string",
+ body: `{{if (eq (.TypeName .Type) "[]byte") -}}
+ string({{.X}})
+ {{- end}}`,
+ },
+ {
+ label: "tostring",
+ details: "int to string",
+ body: `{{if (eq (.TypeName .Type) "int") -}}
+ {{.Import "strconv"}}.Itoa({{.X}})
+ {{- end}}`,
+ },
+ {
+ label: "tobytes",
+ details: "string to []byte",
+ body: `{{if (eq (.TypeName .Type) "string") -}}
+ []byte({{.X}})
+ {{- end}}`,
+ },
+}
// Cursor indicates where the client's cursor should end up after the
// snippet is done.
diff --git a/gopls/internal/golang/completion/unify.go b/gopls/internal/golang/completion/unify.go
index 8f4a1d3cbe0..f28ad49cd52 100644
--- a/gopls/internal/golang/completion/unify.go
+++ b/gopls/internal/golang/completion/unify.go
@@ -189,29 +189,6 @@ func (u *unifier) set(x *types.TypeParam, t types.Type) {
*u.handles[x] = t
}
-// unknowns returns the number of type parameters for which no type has been set yet.
-func (u *unifier) unknowns() int {
- n := 0
- for _, h := range u.handles {
- if *h == nil {
- n++
- }
- }
- return n
-}
-
-// inferred returns the list of inferred types for the given type parameter list.
-// The result is never nil and has the same length as tparams; result types that
-// could not be inferred are nil. Corresponding type parameters and result types
-// have identical indices.
-func (u *unifier) inferred(tparams []*types.TypeParam) []types.Type {
- list := make([]types.Type, len(tparams))
- for i, x := range tparams {
- list[i] = u.at(x)
- }
- return list
-}
-
// asInterface returns the underlying type of x as an interface if
// it is a non-type parameter interface. Otherwise it returns nil.
func asInterface(x types.Type) (i *types.Interface) {
@@ -245,30 +222,6 @@ func identicalOrigin(x, y *types.Named) bool {
return x.Origin().Obj() == y.Origin().Obj()
}
-func match(x, y types.Type) types.Type {
- // Common case: we don't have channels.
- if types.Identical(x, y) {
- return x
- }
-
- // We may have channels that differ in direction only.
- if x, _ := x.(*types.Chan); x != nil {
- if y, _ := y.(*types.Chan); y != nil && types.Identical(x.Elem(), y.Elem()) {
- // We have channels that differ in direction only.
- // If there's an unrestricted channel, select the restricted one.
- switch {
- case x.Dir() == types.SendRecv:
- return y
- case y.Dir() == types.SendRecv:
- return x
- }
- }
- }
-
- // types are different
- return nil
-}
-
func coreType(t types.Type) types.Type {
t = types.Unalias(t)
tpar, _ := t.(*types.TypeParam)
diff --git a/gopls/internal/golang/extract.go b/gopls/internal/golang/extract.go
index f73e772e676..5e82e430225 100644
--- a/gopls/internal/golang/extract.go
+++ b/gopls/internal/golang/extract.go
@@ -15,6 +15,7 @@ import (
"go/types"
"slices"
"sort"
+ "strconv"
"strings"
"text/scanner"
@@ -487,10 +488,8 @@ func canExtractVariable(info *types.Info, curFile cursor.Cursor, start, end toke
path, _ := astutil.PathEnclosingInterval(file, e.Pos(), e.End())
for _, n := range path {
if assignment, ok := n.(*ast.AssignStmt); ok {
- for _, lhs := range assignment.Lhs {
- if lhs == e {
- return nil, fmt.Errorf("node %T is in LHS of an AssignStmt", expr)
- }
+ if slices.Contains(assignment.Lhs, e) {
+ return nil, fmt.Errorf("node %T is in LHS of an AssignStmt", expr)
}
break
}
@@ -914,6 +913,123 @@ func extractFunctionMethod(cpkg *cache.Package, pgf *parsego.File, start, end to
}
}
+ // Determine if the extracted block contains any free branch statements, for
+ // example: "continue label" where "label" is declared outside of the
+ // extracted block, or continue inside a "for" statement where the for
+ // statement is declared outside of the extracted block.
+
+ // If the extracted block contains free branch statements, we add another
+ // return value "ctrl" to the extracted function that will be used to
+ // determine the control flow. See the following example, where === denotes
+ // the range to be extracted.
+ //
+ // Before:
+ // func f(cond bool) {
+ // for range "abc" {
+ // ==============
+ // if cond {
+ // continue
+ // }
+ // ==============
+ // println(0)
+ // }
+ // }
+
+ // After:
+ // func f(cond bool) {
+ // for range "abc" {
+ // ctrl := newFunction(cond)
+ // switch ctrl {
+ // case 1:
+ // continue
+ // }
+ // println(0)
+ // }
+ // }
+ //
+ // func newFunction(cond bool) int {
+ // if cond {
+ // return 1
+ // }
+ // return 0
+ // }
+ //
+
+ curSel, _ := pgf.Cursor.FindByPos(start, end) // since canExtractFunction succeeded, this will always return a valid cursor
+ freeBranches := freeBranches(info, curSel, start, end)
+
+ // Generate an unused identifier for the control value.
+ ctrlVar, _ := freshName(info, file, start, "ctrl", 0)
+ if len(freeBranches) > 0 {
+
+ zeroValExpr := &ast.BasicLit{
+ Kind: token.INT,
+ Value: "0",
+ }
+ var branchStmts []*ast.BranchStmt
+ var stack []ast.Node
+ // Add the zero "ctrl" value to each return statement in the extracted block.
+ ast.Inspect(extractedBlock, func(n ast.Node) bool {
+ if n != nil {
+ stack = append(stack, n)
+ } else {
+ stack = stack[:len(stack)-1]
+ }
+ switch n := n.(type) {
+ case *ast.ReturnStmt:
+ n.Results = append(n.Results, zeroValExpr)
+ case *ast.BranchStmt:
+ // Collect a list of branch statements in the extracted block to examine later.
+ if isFreeBranchStmt(stack) {
+ branchStmts = append(branchStmts, n)
+ }
+ case *ast.FuncLit:
+ // Don't descend into nested functions. When we return false
+ // here, ast.Inspect does not give us a "pop" event when leaving
+ // the subtree, so we need to pop here. (golang/go#73319)
+ stack = stack[:len(stack)-1]
+ return false
+ }
+ return true
+ })
+
+ // Construct a return statement to replace each free branch statement in the extracted block. It should have
+ // zero values for all return parameters except one, "ctrl", which dictates which continuation to follow.
+ var freeCtrlStmtReturns []ast.Expr
+ // Create "zero values" for each type.
+ for _, returnType := range returnTypes {
+ var val ast.Expr
+ var isValid bool
+ for obj, typ := range seenVars {
+ if typ == returnType.Type {
+ val, isValid = typesinternal.ZeroExpr(obj.Type(), qual)
+ break
+ }
+ }
+ if !isValid {
+ return nil, nil, fmt.Errorf("could not find matching AST expression for %T", returnType.Type)
+ }
+ freeCtrlStmtReturns = append(freeCtrlStmtReturns, val)
+ }
+ freeCtrlStmtReturns = append(freeCtrlStmtReturns, getZeroVals(retVars)...)
+
+ for i, branchStmt := range branchStmts {
+ replaceBranchStmtWithReturnStmt(extractedBlock, branchStmt, &ast.ReturnStmt{
+ Return: branchStmt.Pos(),
+ Results: append(slices.Clip(freeCtrlStmtReturns), &ast.BasicLit{
+ Kind: token.INT,
+ Value: strconv.Itoa(i + 1), // start with 1 because 0 is reserved for base case
+ }),
+ })
+
+ }
+ retVars = append(retVars, &returnVariable{
+ name: ast.NewIdent(ctrlVar),
+ decl: &ast.Field{Type: ast.NewIdent("int")},
+ zeroVal: zeroValExpr,
+ })
+ }
+
// Add a return statement to the end of the new function. This return statement must include
// the values for the types of the original extracted function signature and (if a return
// statement is present in the selection) enclosing function signature.
@@ -1042,6 +1158,22 @@ func extractFunctionMethod(cpkg *cache.Package, pgf *parsego.File, start, end to
strings.ReplaceAll(ifBuf.String(), "\n", newLineIndent)
fullReplacement.WriteString(ifstatement)
}
+
+ // Add the switch statement for free branch statements after the new function call.
+ if len(freeBranches) > 0 {
+ fmt.Fprintf(&fullReplacement, "%[1]sswitch %[2]s {%[1]s", newLineIndent, ctrlVar)
+ for i, br := range freeBranches {
+ // Preserve spacing at the beginning of the line containing the branch statement.
+ startPos := tok.LineStart(safetoken.Line(tok, br.Pos()))
+ start, end, err := safetoken.Offsets(tok, startPos, br.End())
+ if err != nil {
+ return nil, nil, err
+ }
+ fmt.Fprintf(&fullReplacement, "case %d:\n%s%s", i+1, pgf.Src[start:end], newLineIndent)
+ }
+ fullReplacement.WriteString("}")
+ }
+
fullReplacement.Write(after)
fullReplacement.WriteString("\n\n") // add newlines after the enclosing function
fullReplacement.Write(newFuncBuf.Bytes()) // insert the extracted function
@@ -1271,6 +1403,9 @@ func collectFreeVars(info *types.Info, file *ast.File, start, end token.Pos, nod
var obj types.Object
var isFree, prune bool
switch n := n.(type) {
+ case *ast.BranchStmt:
+ // Avoid including labels attached to branch statements.
+ return false
case *ast.Ident:
obj, isFree = id(n)
case *ast.SelectorExpr:
@@ -1706,8 +1841,8 @@ func varNameForType(t types.Type) (string, bool) {
return AbbreviateVarName(typeName), true
}
-// adjustReturnStatements adds "zero values" of the given types to each return statement
-// in the given AST node.
+// adjustReturnStatements adds "zero values" of the given types to each return
+// statement in the given AST node.
func adjustReturnStatements(returnTypes []*ast.Field, seenVars map[types.Object]ast.Expr, extractedBlock *ast.BlockStmt, qual types.Qualifier) error {
var zeroVals []ast.Expr
// Create "zero values" for each type.
@@ -1715,11 +1850,10 @@ func adjustReturnStatements(returnTypes []*ast.Field, seenVars map[types.Object]
var val ast.Expr
var isValid bool
for obj, typ := range seenVars {
- if typ != returnType.Type {
- continue
+ if typ == returnType.Type {
+ val, isValid = typesinternal.ZeroExpr(obj.Type(), qual)
+ break
}
- val, isValid = typesinternal.ZeroExpr(obj.Type(), qual)
- break
}
if !isValid {
return fmt.Errorf("could not find matching AST expression for %T", returnType.Type)
@@ -1860,3 +1994,122 @@ func cond[T any](cond bool, t, f T) T {
return f
}
}
+
+// replaceBranchStmtWithReturnStmt modifies the ast node to replace the given
+// branch statement with the given return statement.
+func replaceBranchStmtWithReturnStmt(block ast.Node, br *ast.BranchStmt, ret *ast.ReturnStmt) {
+ ast.Inspect(block, func(n ast.Node) bool {
+ // Look for the branch statement within a BlockStmt or CaseClause.
+ switch n := n.(type) {
+ case *ast.BlockStmt:
+ for i, stmt := range n.List {
+ if stmt == br {
+ n.List[i] = ret
+ return false
+ }
+ }
+ case *ast.CaseClause:
+ for i, stmt := range n.Body {
+ if stmt.Pos() == br.Pos() {
+ n.Body[i] = ret
+ return false
+ }
+ }
+ }
+ return true
+ })
+}
+
+// freeBranches returns all branch statements beneath cur whose continuation
+// lies outside the (start, end) range.
+func freeBranches(info *types.Info, cur cursor.Cursor, start, end token.Pos) (free []*ast.BranchStmt) {
+nextBranch:
+ for curBr := range cur.Preorder((*ast.BranchStmt)(nil)) {
+ br := curBr.Node().(*ast.BranchStmt)
+ if br.End() < start || br.Pos() > end {
+ continue
+ }
+ label, _ := info.Uses[br.Label].(*types.Label)
+ if label != nil && !(start <= label.Pos() && label.Pos() <= end) {
+ free = append(free, br)
+ continue
+ }
+ if br.Tok == token.BREAK || br.Tok == token.CONTINUE {
+ filter := []ast.Node{
+ (*ast.ForStmt)(nil),
+ (*ast.RangeStmt)(nil),
+ (*ast.SwitchStmt)(nil),
+ (*ast.TypeSwitchStmt)(nil),
+ (*ast.SelectStmt)(nil),
+ }
+ // Find innermost relevant ancestor for break/continue.
+ for curAncestor := range curBr.Parent().Enclosing(filter...) {
+ if l, ok := curAncestor.Parent().Node().(*ast.LabeledStmt); ok &&
+ label != nil &&
+ l.Label.Name == label.Name() {
+ continue
+ }
+ switch n := curAncestor.Node().(type) {
+ case *ast.ForStmt, *ast.RangeStmt:
+ if n.Pos() < start {
+ free = append(free, br)
+ }
+ continue nextBranch
+ case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
+ if br.Tok == token.BREAK {
+ if n.Pos() < start {
+ free = append(free, br)
+ }
+ continue nextBranch
+ }
+ }
+ }
+ }
+ }
+ return
+}
+
+// isFreeBranchStmt returns true if the relevant ancestor for the branch
+// statement at stack[len(stack)-1] cannot be found in the stack. This is used
+// when we are examining the extracted block, since type information isn't
+// available. We need to find the location of the label without using
+// types.Info.
+func isFreeBranchStmt(stack []ast.Node) bool {
+ switch node := stack[len(stack)-1].(type) {
+ case *ast.BranchStmt:
+ isLabeled := node.Label != nil
+ switch node.Tok {
+ case token.GOTO:
+ if isLabeled {
+ return !enclosingLabel(stack, node.Label.Name)
+ }
+ case token.BREAK, token.CONTINUE:
+ // Find innermost relevant ancestor for break/continue.
+ for i := len(stack) - 2; i >= 0; i-- {
+ n := stack[i]
+ if isLabeled {
+ l, ok := n.(*ast.LabeledStmt)
+ if !(ok && l.Label.Name == node.Label.Name) {
+ continue
+ }
+ }
+ switch n.(type) {
+ case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
+ return false
+ }
+ }
+ }
+ }
+ // We didn't find the relevant ancestor on the path, so this must be a free branch statement.
+ return true
+}
+
+// enclosingLabel returns true if the given label is found on the stack.
+func enclosingLabel(stack []ast.Node, label string) bool {
+ for _, n := range stack {
+ if labelStmt, ok := n.(*ast.LabeledStmt); ok && labelStmt.Label.Name == label {
+ return true
+ }
+ }
+ return false
+}
diff --git a/gopls/internal/golang/extracttofile.go b/gopls/internal/golang/extracttofile.go
index d3026d4ee0f..cc833f12c42 100644
--- a/gopls/internal/golang/extracttofile.go
+++ b/gopls/internal/golang/extracttofile.go
@@ -93,6 +93,7 @@ func ExtractToNewFile(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
return nil, fmt.Errorf("%s: %w", errorPrefix, err)
}
+ // Expand the selection, and compute the portion to extract.
start, end, firstSymbol, ok := selectedToplevelDecls(pgf, start, end)
if !ok {
return nil, fmt.Errorf("invalid selection")
@@ -109,7 +110,20 @@ func ExtractToNewFile(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
spaces := len(rest) - len(bytes.TrimLeft(rest, " \t\n"))
end += token.Pos(spaces)
pgf.CheckPos(end) // #70553
- // Inv: end is valid wrt pgf.Tok.
+ if !(start <= end) {
+ bug.Reportf("start: not before end")
+ }
+ // Inv: end is valid wrt pgf.Tok; env >= start.
+ fileStart := pgf.File.FileStart
+ pgf.CheckPos(fileStart) // #70553
+ if !(0 <= start-fileStart) {
+ bug.Reportf("start: out of bounds")
+ }
+ if !(int(end-fileStart) <= len(pgf.Src)) {
+ bug.Reportf("end: out of bounds")
+ }
+ // Inv: 0 <= start-fileStart <= end-fileStart <= len(Src).
+ src := pgf.Src[start-fileStart : end-fileStart]
replaceRange, err := pgf.PosRange(start, end)
if err != nil {
@@ -176,9 +190,7 @@ func ExtractToNewFile(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
return nil, fmt.Errorf("%s: %w", errorPrefix, err)
}
- fileStart := pgf.File.FileStart
- pgf.CheckPos(fileStart) // #70553
- buf.Write(pgf.Src[start-fileStart : end-fileStart])
+ buf.Write(src)
newFileContent, err := format.Source(buf.Bytes())
if err != nil {
diff --git a/gopls/internal/golang/hover.go b/gopls/internal/golang/hover.go
index c3fecd1c9d1..93c89f3af97 100644
--- a/gopls/internal/golang/hover.go
+++ b/gopls/internal/golang/hover.go
@@ -38,6 +38,7 @@ import (
gastutil "golang.org/x/tools/gopls/internal/util/astutil"
"golang.org/x/tools/gopls/internal/util/bug"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ internalastutil "golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/tokeninternal"
@@ -286,6 +287,10 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
}
}
+ // By convention, we qualify hover information relative to the package
+ // from which the request originated.
+ qual := typesinternal.FileQualifier(pgf.File, pkg.Types())
+
// Handle hover over identifier.
// The general case: compute hover information for the object referenced by
@@ -304,10 +309,6 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
hoverRange = &rng
}
- // By convention, we qualify hover information relative to the package
- // from which the request originated.
- qual := typesinternal.FileQualifier(pgf.File, pkg.Types())
-
// Handle type switch identifiers as a special case, since they don't have an
// object.
//
@@ -343,6 +344,42 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
// By default, types.ObjectString provides a reasonable signature.
signature := objectString(obj, qual, declPos, declPGF.Tok, spec)
+
+ // When hovering over a reference to a promoted struct field,
+ // show the implicitly selected intervening fields.
+ cur, ok := pgf.Cursor.FindByPos(pos, pos)
+ if !ok {
+ return protocol.Range{}, nil, fmt.Errorf("Invalid hover position, failed to get cursor")
+ }
+ if obj, ok := obj.(*types.Var); ok && obj.IsField() {
+ if selExpr, ok := cur.Parent().Node().(*ast.SelectorExpr); ok {
+ sel := pkg.TypesInfo().Selections[selExpr]
+ if len(sel.Index()) > 1 {
+ var buf bytes.Buffer
+ buf.WriteString(" // through ")
+ t := typesinternal.Unpointer(sel.Recv())
+ for i, index := range sel.Index()[:len(sel.Index())-1] {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ field := typesinternal.Unpointer(t.Underlying()).(*types.Struct).Field(index)
+ t = field.Type()
+ // Inv: fieldType is N or *N for some NamedOrAlias type N.
+ if ptr, ok := t.(*types.Pointer); ok {
+ buf.WriteString("*")
+ t = ptr.Elem()
+ }
+ // Be defensive in case of ill-typed code:
+ if named, ok := t.(typesinternal.NamedOrAlias); ok {
+ buf.WriteString(named.Obj().Name())
+ }
+ }
+ // Update signature to include embedded struct info.
+ signature += buf.String()
+ }
+ }
+ }
+
singleLineSignature := signature
// Display struct tag for struct fields at the end of the signature.
@@ -614,7 +651,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
linkPath = ""
} else if linkMeta.Module != nil && linkMeta.Module.Version != "" {
mod := linkMeta.Module
- linkPath = strings.Replace(linkPath, mod.Path, mod.Path+"@"+mod.Version, 1)
+ linkPath = strings.Replace(linkPath, mod.Path, cache.ResolvedString(mod), 1)
}
var footer string
@@ -1502,16 +1539,10 @@ func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spe
stack := make([]ast.Node, 0, 20)
// Allocate the closure once, outside the loop.
- f := func(n ast.Node) bool {
+ f := func(n ast.Node, stack []ast.Node) bool {
if found {
return false
}
- if n != nil {
- stack = append(stack, n) // push
- } else {
- stack = stack[:len(stack)-1] // pop
- return false
- }
// Skip subtrees (incl. files) that don't contain the search point.
if !(n.Pos() <= pos && pos < n.End()) {
@@ -1596,7 +1627,7 @@ func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spe
return true
}
for _, file := range files {
- ast.Inspect(file, f)
+ internalastutil.PreorderStack(file, stack, f)
if found {
return decl, spec, field
}
diff --git a/gopls/internal/golang/identifier_test.go b/gopls/internal/golang/identifier_test.go
index 8206d8731ae..0823793466f 100644
--- a/gopls/internal/golang/identifier_test.go
+++ b/gopls/internal/golang/identifier_test.go
@@ -41,7 +41,6 @@ func TestSearchForEnclosing(t *testing.T) {
}
for _, test := range tests {
- test := test
t.Run(test.desc, func(t *testing.T) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors|parser.SkipObjectResolution)
diff --git a/gopls/internal/golang/implementation.go b/gopls/internal/golang/implementation.go
index a5ab5d19a13..675b232d0eb 100644
--- a/gopls/internal/golang/implementation.go
+++ b/gopls/internal/golang/implementation.go
@@ -14,7 +14,6 @@ import (
"iter"
"reflect"
"slices"
- "sort"
"strings"
"sync"
@@ -64,19 +63,8 @@ func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
if err != nil {
return nil, err
}
-
- // Sort and de-duplicate locations.
- sort.Slice(locs, func(i, j int) bool {
- return protocol.CompareLocation(locs[i], locs[j]) < 0
- })
- out := locs[:0]
- for _, loc := range locs {
- if len(out) == 0 || out[len(out)-1] != loc {
- out = append(out, loc)
- }
- }
- locs = out
-
+ slices.SortFunc(locs, protocol.CompareLocation)
+ locs = slices.Compact(locs) // de-duplicate
return locs, nil
}
@@ -97,12 +85,46 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
}
// Find implementations based on method sets.
+ var (
+ locsMu sync.Mutex
+ locs []protocol.Location
+ )
+ // relation=0 here means infer direction of the relation
+ // (Supertypes/Subtypes) from concreteness of query type/method.
+ // (Ideally the implementations request would provide directionality
+ // so that one could ask for, say, the superinterfaces of io.ReadCloser;
+ // see https://github.com/golang/go/issues/68641#issuecomment-2269293762.)
+ const relation = methodsets.TypeRelation(0)
+ err = implementationsMsets(ctx, snapshot, pkg, pgf, pos, relation, func(_ metadata.PackagePath, _ string, _ bool, loc protocol.Location) {
+ locsMu.Lock()
+ locs = append(locs, loc)
+ locsMu.Unlock()
+ })
+ return locs, err
+}
+
+// An implYieldFunc is a callback called for each match produced by the implementation machinery.
+// - name describes the type or method.
+// - abstract indicates that the result is an interface type or interface method.
+//
+// implYieldFunc implementations must be concurrency-safe.
+type implYieldFunc func(pkgpath metadata.PackagePath, name string, abstract bool, loc protocol.Location)
+// implementationsMsets computes implementations of the type at the
+// specified position, by method sets.
+//
+// rel specifies the desired direction of the relation: Subtype,
+// Supertype, or both. As a special case, zero means infer the
+// direction from the concreteness of the query object: Supertype for
+// a concrete type, Subtype for an interface.
+//
+// It is shared by Implementations and TypeHierarchy.
+func implementationsMsets(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, pos token.Pos, rel methodsets.TypeRelation, yield implYieldFunc) error {
// First, find the object referenced at the cursor.
// The object may be declared in a different package.
- obj, err := implementsObj(pkg, pgf, pos)
+ obj, err := implementsObj(pkg.TypesInfo(), pgf.File, pos)
if err != nil {
- return nil, err
+ return err
}
// If the resulting object has a position, we can expand the search to types
@@ -123,11 +145,11 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
declURI = protocol.URIFromPath(declPosn.Filename)
declMPs, err := snapshot.MetadataForFile(ctx, declURI)
if err != nil {
- return nil, err
+ return err
}
metadata.RemoveIntermediateTestVariants(&declMPs)
if len(declMPs) == 0 {
- return nil, fmt.Errorf("no packages for file %s", declURI)
+ return fmt.Errorf("no packages for file %s", declURI)
}
ids := make([]PackageID, len(declMPs))
for i, mp := range declMPs {
@@ -135,7 +157,7 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
}
localPkgs, err = snapshot.TypeCheck(ctx, ids...)
if err != nil {
- return nil, err
+ return err
}
}
@@ -143,23 +165,9 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
// Is the selected identifier a type name or method?
// (For methods, report the corresponding method names.)
- //
- // This logic is reused for local queries.
- typeOrMethod := func(obj types.Object) (types.Type, *types.Func) {
- switch obj := obj.(type) {
- case *types.TypeName:
- return obj.Type(), nil
- case *types.Func:
- // For methods, use the receiver type, which may be anonymous.
- if recv := obj.Signature().Recv(); recv != nil {
- return recv.Type(), obj
- }
- }
- return nil, nil
- }
queryType, queryMethod := typeOrMethod(obj)
if queryType == nil {
- return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj
+ return bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj
}
// Compute the method-set fingerprint used as a key to the global search.
@@ -167,7 +175,15 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
if !hasMethods {
// A type with no methods yields an empty result.
// (No point reporting that every type satisfies 'any'.)
- return nil, nil
+ return nil
+ }
+
+ // If the client specified no relation, infer it
+ // from the concreteness of the query type.
+ if rel == 0 {
+ rel = cond(types.IsInterface(queryType),
+ methodsets.Subtype,
+ methodsets.Supertype)
}
// The global search needs to look at every package in the
@@ -181,7 +197,7 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
// be optimized by being applied as soon as each package is available.
globalMetas, err := snapshot.AllMetadata(ctx)
if err != nil {
- return nil, err
+ return err
}
metadata.RemoveIntermediateTestVariants(&globalMetas)
globalIDs := make([]PackageID, 0, len(globalMetas))
@@ -198,36 +214,32 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
}
indexes, err := snapshot.MethodSets(ctx, globalIDs...)
if err != nil {
- return nil, fmt.Errorf("querying method sets: %v", err)
+ return fmt.Errorf("querying method sets: %v", err)
}
// Search local and global packages in parallel.
- var (
- group errgroup.Group
- locsMu sync.Mutex
- locs []protocol.Location
- )
+ var group errgroup.Group
+
// local search
- for _, localPkg := range localPkgs {
+ for _, pkg := range localPkgs {
// The localImplementations algorithm assumes needle and haystack
// belong to a single package (="realm" of types symbol identities),
// so we need to recompute obj for each local package.
// (By contrast the global algorithm is name-based.)
- declPkg := localPkg
group.Go(func() error {
- pkgID := declPkg.Metadata().ID
- declFile, err := declPkg.File(declURI)
+ pkgID := pkg.Metadata().ID
+
+ // Find declaring identifier based on (URI, offset)
+ // so that localImplementations can locate the
+ // corresponding obj/queryType/queryMethod in pkg.
+ declFile, err := pkg.File(declURI)
if err != nil {
return err // "can't happen"
}
-
- // Find declaration of corresponding object
- // in this package based on (URI, offset).
pos, err := safetoken.Pos(declFile.Tok, declOffset)
if err != nil {
return err // also "can't happen"
}
- // TODO(adonovan): simplify: use objectsAt?
path := pathEnclosingObjNode(declFile.File, pos)
if path == nil {
return ErrNoIdentFound // checked earlier
@@ -236,27 +248,16 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
if !ok {
return ErrNoIdentFound // checked earlier
}
- // Shadow obj, queryType, and queryMethod in this package.
- obj := declPkg.TypesInfo().ObjectOf(id) // may be nil
- queryType, queryMethod := typeOrMethod(obj)
- if queryType == nil {
- return fmt.Errorf("querying method sets in package %q: %v", pkgID, err)
- }
- localLocs, err := localImplementations(ctx, snapshot, declPkg, queryType, queryMethod)
- if err != nil {
+ if err := localImplementations(ctx, snapshot, pkg, id, rel, yield); err != nil {
return fmt.Errorf("querying local implementations %q: %v", pkgID, err)
}
- locsMu.Lock()
- locs = append(locs, localLocs...)
- locsMu.Unlock()
return nil
})
}
// global search
for _, index := range indexes {
- index := index
group.Go(func() error {
- for _, res := range index.Search(key, queryMethod) {
+ for _, res := range index.Search(key, rel, queryMethod) {
loc := res.Location
// Map offsets to protocol.Locations in parallel (may involve I/O).
group.Go(func() error {
@@ -264,20 +265,33 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
if err != nil {
return err
}
- locsMu.Lock()
- locs = append(locs, ploc)
- locsMu.Unlock()
+ yield(index.PkgPath, res.TypeName, res.IsInterface, ploc)
return nil
})
}
return nil
})
}
- if err := group.Wait(); err != nil {
- return nil, err
- }
+ return group.Wait()
+}
- return locs, nil
+// typeOrMethod returns the type and optional method to use in an
+// Implementations operation on the specified symbol.
+// It returns a nil type to indicate that the query should not proceed.
+//
+// (It is factored out to allow it to be used both in the query package
+// then (in [localImplementations]) again in the declarating package.)
+func typeOrMethod(obj types.Object) (types.Type, *types.Func) {
+ switch obj := obj.(type) {
+ case *types.TypeName:
+ return obj.Type(), nil
+ case *types.Func:
+ // For methods, use the receiver type, which may be anonymous.
+ if recv := obj.Signature().Recv(); recv != nil {
+ return recv.Type(), obj
+ }
+ }
+ return nil, nil
}
// offsetToLocation converts an offset-based position to a protocol.Location,
@@ -298,7 +312,7 @@ func offsetToLocation(ctx context.Context, snapshot *cache.Snapshot, filename st
// implementsObj returns the object to query for implementations,
// which is a type name or method.
-func implementsObj(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.Object, error) {
+func implementsObj(info *types.Info, file *ast.File, pos token.Pos) (types.Object, error) {
// This function inherits the limitation of its predecessor in
// requiring the selection to be an identifier (of a type or
// method). But there's no fundamental reason why one could
@@ -309,7 +323,7 @@ func implementsObj(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.
// subexpression such as x.f().)
// TODO(adonovan): simplify: use objectsAt?
- path := pathEnclosingObjNode(pgf.File, pos)
+ path := pathEnclosingObjNode(file, pos)
if path == nil {
return nil, ErrNoIdentFound
}
@@ -319,12 +333,12 @@ func implementsObj(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.
}
// Is the object a type or method? Reject other kinds.
- obj := pkg.TypesInfo().Uses[id]
+ obj := info.Uses[id]
if obj == nil {
// Check uses first (unlike ObjectOf) so that T in
// struct{T} is treated as a reference to a type,
// not a declaration of a field.
- obj = pkg.TypesInfo().Defs[id]
+ obj = info.Defs[id]
}
switch obj := obj.(type) {
case *types.TypeName:
@@ -346,8 +360,9 @@ func implementsObj(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.
}
// localImplementations searches within pkg for declarations of all
-// types that are assignable to/from the query type, and returns a new
-// unordered array of their locations.
+// supertypes (if rel contains Supertype) or subtypes (if rel contains
+// Subtype) of the type or method declared by id within the same
+// package, and returns a new unordered array of their locations.
//
// If method is non-nil, the function instead returns the location
// of each type's method (if any) of that ID.
@@ -356,17 +371,41 @@ func implementsObj(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.
// function's results may include type declarations that are local to
// a function body. The global search index excludes such types
// because reliably naming such types is hard.)
-func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, method *types.Func) ([]protocol.Location, error) {
+//
+// Results are reported via the the yield function.
+func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, id *ast.Ident, rel methodsets.TypeRelation, yield implYieldFunc) error {
+ queryType, queryMethod := typeOrMethod(pkg.TypesInfo().Defs[id])
+ if queryType == nil {
+ return bug.Errorf("can't find corresponding symbol for %q in package %q", id.Name, pkg)
+ }
queryType = methodsets.EnsurePointer(queryType)
var msets typeutil.MethodSetCache
+ matches := func(candidateType types.Type) bool {
+ // Test the direction of the relation.
+ // The client may request either direction or both
+ // (e.g. when the client is References),
+ // and the Result reports each test independently;
+ // both tests succeed when comparing identical
+ // interface types.
+ var got methodsets.TypeRelation
+ if rel&methodsets.Supertype != 0 && implements(&msets, queryType, candidateType) {
+ got |= methodsets.Supertype
+ }
+ if rel&methodsets.Subtype != 0 && implements(&msets, candidateType, queryType) {
+ got |= methodsets.Subtype
+ }
+ return got != 0
+ }
+
// Scan through all type declarations in the syntax.
- var locs []protocol.Location
- var methodLocs []methodsets.Location
for _, pgf := range pkg.CompiledGoFiles() {
for cur := range pgf.Cursor.Preorder((*ast.TypeSpec)(nil)) {
spec := cur.Node().(*ast.TypeSpec)
+ if spec.Name == id {
+ continue // avoid self-comparison of query type
+ }
def := pkg.TypesInfo().Defs[spec.Name]
if def == nil {
continue // "can't happen" for types
@@ -375,15 +414,8 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca
continue // skip type aliases to avoid duplicate reporting
}
candidateType := methodsets.EnsurePointer(def.Type())
-
- // The historical behavior enshrined by this
- // function rejects cases where both are
- // (nontrivial) interface types?
- // That seems like useful information; see #68641.
- // TODO(adonovan): UX: report I/I pairs too?
- // The same question appears in the global algorithm (methodsets).
- if !concreteImplementsIntf(&msets, candidateType, queryType) {
- continue // not assignable
+ if !matches(candidateType) {
+ continue
}
// Ignore types with empty method sets.
@@ -393,9 +425,12 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca
continue
}
- if method == nil {
+ isInterface := types.IsInterface(def.Type())
+
+ if queryMethod == nil {
// Found matching type.
- locs = append(locs, mustLocation(pgf, spec.Name))
+ loc := mustLocation(pgf, spec.Name)
+ yield(pkg.Metadata().PkgPath, spec.Name.Name, isInterface, loc)
continue
}
@@ -407,41 +442,39 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca
// but it's easier to walk the method set.
for i := 0; i < mset.Len(); i++ {
m := mset.At(i).Obj()
- if m.Id() == method.Id() {
+ if m.Pos() == id.Pos() {
+ continue // avoid self-comparison of query method
+ }
+ if m.Id() == queryMethod.Id() {
posn := safetoken.StartPosition(pkg.FileSet(), m.Pos())
- methodLocs = append(methodLocs, methodsets.Location{
- Filename: posn.Filename,
- Start: posn.Offset,
- End: posn.Offset + len(m.Name()),
- })
+ loc, err := offsetToLocation(ctx, snapshot, posn.Filename, posn.Offset, posn.Offset+len(m.Name()))
+ if err != nil {
+ return err
+ }
+ yield(pkg.Metadata().PkgPath, m.Name(), isInterface, loc)
break
}
}
}
}
- // Finally convert method positions to protocol form by reading the files.
- for _, mloc := range methodLocs {
- loc, err := offsetToLocation(ctx, snapshot, mloc.Filename, mloc.Start, mloc.End)
- if err != nil {
- return nil, err
- }
- locs = append(locs, loc)
- }
-
- // Special case: for types that satisfy error, report builtin.go (see #59527).
- if types.Implements(queryType, errorInterfaceType) {
+ // Special case: for types that satisfy error,
+ // report error in builtin.go (see #59527).
+ //
+ // (An inconsistency: we always report the type error
+ // even when the query was for the method error.Error.)
+ if matches(errorType) {
loc, err := errorLocation(ctx, snapshot)
if err != nil {
- return nil, err
+ return err
}
- locs = append(locs, loc)
+ yield("", "error", true, loc)
}
- return locs, nil
+ return nil
}
-var errorInterfaceType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
+var errorType = types.Universe.Lookup("error").Type()
// errorLocation returns the location of the 'error' type in builtin.go.
func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Location, error) {
@@ -461,28 +494,14 @@ func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Loca
return protocol.Location{}, fmt.Errorf("built-in error type not found")
}
-// concreteImplementsIntf reports whether x is an interface type
-// implemented by concrete type y, or vice versa.
-//
+// implements reports whether x implements y.
// If one or both types are generic, the result indicates whether the
// interface may be implemented under some instantiation.
-func concreteImplementsIntf(msets *typeutil.MethodSetCache, x, y types.Type) bool {
- xiface := types.IsInterface(x)
- yiface := types.IsInterface(y)
-
- // Make sure exactly one is an interface type.
- // TODO(adonovan): rescind this policy choice and report
- // I/I relationships. See CL 619719 + issue #68641.
- if xiface == yiface {
+func implements(msets *typeutil.MethodSetCache, x, y types.Type) bool {
+ if !types.IsInterface(y) {
return false
}
- // Rearrange if needed so x is the concrete type.
- if xiface {
- x, y = y, x
- }
- // Inv: y is an interface type.
-
// For each interface method of y, check that x has it too.
// It is not necessary to compute x's complete method set.
//
@@ -932,7 +951,7 @@ func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node {
// implFuncs returns errNotHandled to indicate that we should try the
// regular method-sets algorithm.
func implFuncs(pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol.Location, error) {
- curSel, ok := pgf.Cursor.FindPos(pos, pos)
+ curSel, ok := pgf.Cursor.FindByPos(pos, pos)
if !ok {
return nil, fmt.Errorf("no code selected")
}
@@ -956,7 +975,12 @@ func implFuncs(pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol
// are inconsistent. Consequently, the ancestors for a "func"
// token of Func{Lit,Decl} do not include FuncType, hence the
// explicit cases below.
- for _, cur := range curSel.Stack(nil) {
+ for cur := range curSel.Enclosing(
+ (*ast.FuncDecl)(nil),
+ (*ast.FuncLit)(nil),
+ (*ast.FuncType)(nil),
+ (*ast.CallExpr)(nil),
+ ) {
switch n := cur.Node().(type) {
case *ast.FuncDecl, *ast.FuncLit:
if inToken(n.Pos(), "func", pos) {
diff --git a/gopls/internal/golang/inlay_hint.go b/gopls/internal/golang/inlay_hint.go
index b49ebd85e21..617231a4f8c 100644
--- a/gopls/internal/golang/inlay_hint.go
+++ b/gopls/internal/golang/inlay_hint.go
@@ -65,7 +65,7 @@ func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pR
}
var hints []protocol.InlayHint
- if curSubrange, ok := pgf.Cursor.FindPos(start, end); ok {
+ if curSubrange, ok := pgf.Cursor.FindByPos(start, end); ok {
add := func(hint protocol.InlayHint) { hints = append(hints, hint) }
for _, fn := range enabledHints {
fn(info, pgf, qual, curSubrange, add)
diff --git a/gopls/internal/golang/invertifcondition.go b/gopls/internal/golang/invertifcondition.go
index 012278df79e..c8cd7deef5e 100644
--- a/gopls/internal/golang/invertifcondition.go
+++ b/gopls/internal/golang/invertifcondition.go
@@ -42,10 +42,7 @@ func invertIfCondition(pkg *cache.Package, pgf *parsego.File, start, end token.P
// version of the original if body
sourcePos := safetoken.StartPosition(fset, ifStatement.Pos())
- indent := sourcePos.Column - 1
- if indent < 0 {
- indent = 0
- }
+ indent := max(sourcePos.Column-1, 0)
standaloneBodyText := ifBodyToStandaloneCode(fset, ifStatement.Body, src)
replaceElse = analysis.TextEdit{
diff --git a/gopls/internal/golang/modify_tags.go b/gopls/internal/golang/modify_tags.go
new file mode 100644
index 00000000000..46748c841d1
--- /dev/null
+++ b/gopls/internal/golang/modify_tags.go
@@ -0,0 +1,84 @@
+// Copyright 2025 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 golang
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/token"
+
+ "github.com/fatih/gomodifytags/modifytags"
+ "golang.org/x/tools/gopls/internal/cache"
+ "golang.org/x/tools/gopls/internal/cache/parsego"
+ "golang.org/x/tools/gopls/internal/file"
+ "golang.org/x/tools/gopls/internal/protocol"
+ "golang.org/x/tools/gopls/internal/protocol/command"
+ "golang.org/x/tools/gopls/internal/util/moreiters"
+ internalastutil "golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/astutil/cursor"
+ "golang.org/x/tools/internal/diff"
+ "golang.org/x/tools/internal/tokeninternal"
+)
+
+// Finds the start and end positions of the enclosing struct or returns an error if none is found.
+func findEnclosingStruct(c cursor.Cursor) (token.Pos, token.Pos, error) {
+ for cur := range c.Enclosing((*ast.StructType)(nil)) {
+ return cur.Node().Pos(), cur.Node().End(), nil
+ }
+ return token.NoPos, token.NoPos, fmt.Errorf("no struct enclosing the given positions")
+}
+
+// ModifyTags applies the given struct tag modifications to the specified struct.
+func ModifyTags(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, args command.ModifyTagsArgs, m *modifytags.Modification) ([]protocol.DocumentChange, error) {
+ pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
+ if err != nil {
+ return nil, fmt.Errorf("error fetching package file: %v", err)
+ }
+ start, end, err := pgf.RangePos(args.Range)
+ if err != nil {
+ return nil, fmt.Errorf("error getting position information: %v", err)
+ }
+ // If the cursor is at a point and not a selection, we should use the entire enclosing struct.
+ if start == end {
+ cur, ok := pgf.Cursor.FindByPos(start, end)
+ if !ok {
+ return nil, fmt.Errorf("error finding start and end positions: %v", err)
+ }
+ curStruct, ok := moreiters.First(cur.Enclosing((*ast.StructType)(nil)))
+ if !ok {
+ return nil, fmt.Errorf("no enclosing struct type")
+ }
+ start, end = curStruct.Node().Pos(), curStruct.Node().End()
+ }
+
+ // Create a copy of the file node in order to avoid race conditions when we modify the node in Apply.
+ cloned := internalastutil.CloneNode(pgf.File)
+ fset := tokeninternal.FileSetFor(pgf.Tok)
+
+ if err = m.Apply(fset, cloned, start, end); err != nil {
+ return nil, fmt.Errorf("could not modify tags: %v", err)
+ }
+
+ // Construct a list of DocumentChanges based on the diff between the formatted node and the
+ // original file content.
+ var after bytes.Buffer
+ if err := format.Node(&after, fset, cloned); err != nil {
+ return nil, err
+ }
+ edits := diff.Bytes(pgf.Src, after.Bytes())
+ if len(edits) == 0 {
+ return nil, nil
+ }
+ textedits, err := protocol.EditsFromDiffEdits(pgf.Mapper, edits)
+ if err != nil {
+ return nil, fmt.Errorf("error computing edits for %s: %v", args.URI, err)
+ }
+ return []protocol.DocumentChange{
+ protocol.DocumentChangeEdit(fh, textedits),
+ }, nil
+}
diff --git a/gopls/internal/golang/pkgdoc.go b/gopls/internal/golang/pkgdoc.go
index 2faff1a1526..9f2b2bf51a4 100644
--- a/gopls/internal/golang/pkgdoc.go
+++ b/gopls/internal/golang/pkgdoc.go
@@ -14,7 +14,7 @@ package golang
// - rewrite using html/template.
// Or factor with golang.org/x/pkgsite/internal/godoc/dochtml.
// - emit breadcrumbs for parent + sibling packages.
-// - list promoted methods---we have type information!
+// - list promoted methods---we have type information! (golang/go#67158)
// - gather Example tests, following go/doc and pkgsite.
// - add option for doc.AllDecls: show non-exported symbols too.
// - style the
bullets in the index as invisible.
@@ -328,7 +328,17 @@ func PackageDocHTML(viewID string, pkg *cache.Package, web Web) ([]byte, error)
filterValues(&t.Vars)
filterFuncs(&t.Funcs)
filterFuncs(&t.Methods)
- return unexported(t.Name)
+ if unexported(t.Name) {
+ // If an unexported type has an exported constructor function,
+ // treat the constructor as an ordinary standalone function.
+ // We will sort Funcs again below.
+ docpkg.Funcs = append(docpkg.Funcs, t.Funcs...)
+ return true // delete this type
+ }
+ return false // keep this type
+ })
+ slices.SortFunc(docpkg.Funcs, func(x, y *doc.Func) int {
+ return strings.Compare(x.Name, y.Name)
})
}
diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go
index 12152453dcd..7fe054a5a7d 100644
--- a/gopls/internal/golang/references.go
+++ b/gopls/internal/golang/references.go
@@ -441,7 +441,6 @@ func ordinaryReferences(ctx context.Context, snapshot *cache.Snapshot, uri proto
// corresponding methods (see above), which expand the global search.
// The target objects are identified by (PkgPath, objectpath).
for id := range expansions {
- id := id
group.Go(func() error {
// TODO(adonovan): opt: batch these TypeChecks.
pkgs, err := snapshot.TypeCheck(ctx, id)
@@ -521,11 +520,11 @@ func expandMethodSearch(ctx context.Context, snapshot *cache.Snapshot, workspace
var mu sync.Mutex // guards addRdeps, targets, expansions
var group errgroup.Group
for i, index := range indexes {
- i := i
index := index
group.Go(func() error {
- // Consult index for matching methods.
- results := index.Search(key, method)
+ // Consult index for matching (super/sub) methods.
+ const want = methodsets.Supertype | methodsets.Subtype
+ results := index.Search(key, want, method)
if len(results) == 0 {
return nil
}
@@ -583,7 +582,7 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo
var msets typeutil.MethodSetCache
// matches reports whether obj either is or corresponds to a target.
- // (Correspondence is defined as usual for interface methods.)
+ // (Correspondence is defined as usual for interface methods: super/subtype.)
matches := func(obj types.Object) bool {
if containsOrigin(targets, obj) {
return true
@@ -591,7 +590,8 @@ func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspo
if methodRecvs != nil && obj.Name() == methodName {
if orecv := effectiveReceiver(obj); orecv != nil {
for _, mrecv := range methodRecvs {
- if concreteImplementsIntf(&msets, orecv, mrecv) {
+ if implements(&msets, orecv, mrecv) ||
+ implements(&msets, mrecv, orecv) {
return true
}
}
diff --git a/gopls/internal/golang/rename.go b/gopls/internal/golang/rename.go
index 26e9d0a5a52..f23f179c6ff 100644
--- a/gopls/internal/golang/rename.go
+++ b/gopls/internal/golang/rename.go
@@ -51,9 +51,11 @@ import (
"go/printer"
"go/token"
"go/types"
+ "maps"
"path"
"path/filepath"
"regexp"
+ "slices"
"sort"
"strconv"
"strings"
@@ -69,8 +71,10 @@ import (
"golang.org/x/tools/gopls/internal/protocol"
goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
"golang.org/x/tools/gopls/internal/util/bug"
+ "golang.org/x/tools/gopls/internal/util/moreiters"
"golang.org/x/tools/gopls/internal/util/safetoken"
internalastutil "golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/astutil/cursor"
"golang.org/x/tools/internal/diff"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/typesinternal"
@@ -167,14 +171,7 @@ func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle,
func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf *parsego.File) (*PrepareItem, error) {
// Does the client support file renaming?
- fileRenameSupported := false
- for _, op := range snapshot.Options().SupportedResourceOperations {
- if op == protocol.Rename {
- fileRenameSupported = true
- break
- }
- }
- if !fileRenameSupported {
+ if !slices.Contains(snapshot.Options().SupportedResourceOperations, protocol.Rename) {
return nil, errors.New("can't rename package: LSP client does not support file renaming")
}
@@ -436,13 +433,7 @@ func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp pro
// become reordered) and that are either identical or
// non-overlapping.
diff.SortEdits(edits)
- filtered := edits[:0]
- for i, edit := range edits {
- if i == 0 || edit != filtered[len(filtered)-1] {
- filtered = append(filtered, edit)
- }
- }
- edits = filtered
+ edits = slices.Compact(edits)
// TODO(adonovan): the logic above handles repeat edits to the
// same file URI (e.g. as a member of package p and p_test) but
@@ -482,6 +473,7 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
// computes the union across all variants.)
var targets map[types.Object]ast.Node
var pkg *cache.Package
+ var cur cursor.Cursor // of selected Ident or ImportSpec
{
mps, err := snapshot.MetadataForFile(ctx, f.URI())
if err != nil {
@@ -505,6 +497,11 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
if err != nil {
return nil, err
}
+ var ok bool
+ cur, ok = pgf.Cursor.FindByPos(pos, pos)
+ if !ok {
+ return nil, fmt.Errorf("can't find cursor for selection")
+ }
objects, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
if err != nil {
return nil, err
@@ -533,7 +530,7 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
//
// Note that unlike Funcs, TypeNames are always canonical (they are "left"
// of the type parameters, unlike methods).
- switch obj.(type) { // avoid "obj :=" since cases reassign the var
+ switch obj0 := obj.(type) { // avoid "obj :=" since cases reassign the var
case *types.TypeName:
if _, ok := types.Unalias(obj.Type()).(*types.TypeParam); ok {
// As with capitalized function parameters below, type parameters are
@@ -541,7 +538,7 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
goto skipObjectPath
}
case *types.Func:
- obj = obj.(*types.Func).Origin()
+ obj = obj0.Origin()
case *types.Var:
// TODO(adonovan): do vars need the origin treatment too? (issue #58462)
@@ -555,7 +552,7 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
// objectpath, the classifies them as local vars, but as
// they came from export data they lack syntax and the
// correct scope tree (issue #61294).
- if !obj.(*types.Var).IsField() && !typesinternal.IsPackageLevel(obj) {
+ if !obj0.IsField() && !typesinternal.IsPackageLevel(obj) {
goto skipObjectPath
}
}
@@ -571,8 +568,36 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
for obj := range targets {
objects = append(objects, obj)
}
+
editMap, _, err := renameObjects(newName, pkg, objects...)
- return editMap, err
+ if err != nil {
+ return nil, err
+ }
+
+ // If the selected identifier is a receiver declaration,
+ // also rename receivers of other methods of the same type
+ // that don't already have the desired name.
+ // Quietly discard edits from any that can't be renamed.
+ //
+ // We interpret renaming the receiver declaration as
+ // intent for the broader renaming; renaming a use of
+ // the receiver effects only the local renaming.
+ if id, ok := cur.Node().(*ast.Ident); ok && id.Pos() == obj.Pos() {
+ if curDecl, ok := moreiters.First(cur.Enclosing((*ast.FuncDecl)(nil))); ok {
+ decl := curDecl.Node().(*ast.FuncDecl) // enclosing func
+ if decl.Recv != nil &&
+ len(decl.Recv.List) > 0 &&
+ len(decl.Recv.List[0].Names) > 0 {
+ recv := pkg.TypesInfo().Defs[decl.Recv.List[0].Names[0]]
+ if recv == obj {
+ // TODO(adonovan): simplify the above 7 lines to
+ // to "if obj.(*Var).Kind==Recv" in go1.25.
+ renameReceivers(pkg, recv.(*types.Var), newName, editMap)
+ }
+ }
+ }
+ }
+ return editMap, nil
}
// Exported: search globally.
@@ -632,6 +657,39 @@ func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
return renameExported(pkgs, declPkgPath, declObjPath, newName)
}
+// renameReceivers renames all receivers of methods of the same named
+// type as recv. The edits of each successful renaming are added to
+// editMap; the failed ones are quietly discarded.
+func renameReceivers(pkg *cache.Package, recv *types.Var, newName string, editMap map[protocol.DocumentURI][]diff.Edit) {
+ _, named := typesinternal.ReceiverNamed(recv)
+ if named == nil {
+ return
+ }
+
+ // Find receivers of other methods of the same named type.
+ for m := range named.Origin().Methods() {
+ recv2 := m.Signature().Recv()
+ if recv2 == recv {
+ continue // don't re-rename original receiver
+ }
+ if recv2.Name() == newName {
+ continue // no renaming needed
+ }
+ editMap2, _, err := renameObjects(newName, pkg, recv2)
+ if err != nil {
+ continue // ignore secondary failures
+ }
+
+ // Since all methods (and their comments)
+ // are disjoint, and don't affect imports,
+ // we can safely assume that all edits are
+ // nonconflicting and disjoint.
+ for uri, edits := range editMap2 {
+ editMap[uri] = append(editMap[uri], edits...)
+ }
+ }
+}
+
// typeCheckReverseDependencies returns the type-checked packages for
// the reverse dependencies of all packages variants containing
// file declURI. The packages are in some topological order.
@@ -657,9 +715,7 @@ func typeCheckReverseDependencies(ctx context.Context, snapshot *cache.Snapshot,
return nil, err
}
allRdeps[variant.ID] = variant // include self
- for id, meta := range rdeps {
- allRdeps[id] = meta
- }
+ maps.Copy(allRdeps, rdeps)
}
var ids []PackageID
for id, meta := range allRdeps {
@@ -1083,7 +1139,12 @@ func renameImports(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.P
continue // not the import we're looking for
}
- pkgname := pkg.TypesInfo().Implicits[imp].(*types.PkgName)
+ pkgname, ok := pkg.TypesInfo().Implicits[imp].(*types.PkgName)
+ if !ok {
+ // "can't happen", but be defensive (#71656)
+ return fmt.Errorf("internal error: missing type information for %s import at %s",
+ imp.Path.Value, safetoken.StartPosition(pkg.FileSet(), imp.Pos()))
+ }
pkgScope := pkg.Types().Scope()
fileScope := pkg.TypesInfo().Scopes[f.File]
diff --git a/gopls/internal/golang/rename_check.go b/gopls/internal/golang/rename_check.go
index 97423fe87a7..6b89cabbe81 100644
--- a/gopls/internal/golang/rename_check.go
+++ b/gopls/internal/golang/rename_check.go
@@ -45,6 +45,8 @@ import (
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ "golang.org/x/tools/internal/astutil/cursor"
+ "golang.org/x/tools/internal/astutil/edge"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/refactor/satisfy"
@@ -338,64 +340,58 @@ func deeper(x, y *types.Scope) bool {
// lexical block enclosing the reference. If fn returns false the
// iteration is terminated and findLexicalRefs returns false.
func forEachLexicalRef(pkg *cache.Package, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool {
+ filter := []ast.Node{
+ (*ast.Ident)(nil),
+ (*ast.SelectorExpr)(nil),
+ (*ast.CompositeLit)(nil),
+ }
ok := true
- var stack []ast.Node
-
- var visit func(n ast.Node) bool
- visit = func(n ast.Node) bool {
- if n == nil {
- stack = stack[:len(stack)-1] // pop
- return false
- }
+ var visit func(cur cursor.Cursor) (descend bool)
+ visit = func(cur cursor.Cursor) (descend bool) {
if !ok {
return false // bail out
}
-
- stack = append(stack, n) // push
- switch n := n.(type) {
+ switch n := cur.Node().(type) {
case *ast.Ident:
if pkg.TypesInfo().Uses[n] == obj {
- block := enclosingBlock(pkg.TypesInfo(), stack)
+ block := enclosingBlock(pkg.TypesInfo(), cur)
if !fn(n, block) {
ok = false
}
}
- return visit(nil) // pop stack
case *ast.SelectorExpr:
// don't visit n.Sel
- ast.Inspect(n.X, visit)
- return visit(nil) // pop stack, don't descend
+ cur.ChildAt(edge.SelectorExpr_X, -1).Inspect(filter, visit)
+ return false // don't descend
case *ast.CompositeLit:
// Handle recursion ourselves for struct literals
// so we don't visit field identifiers.
tv, ok := pkg.TypesInfo().Types[n]
if !ok {
- return visit(nil) // pop stack, don't descend
+ return false // don't descend
}
if is[*types.Struct](typeparams.CoreType(typeparams.Deref(tv.Type))) {
if n.Type != nil {
- ast.Inspect(n.Type, visit)
+ cur.ChildAt(edge.CompositeLit_Type, -1).Inspect(filter, visit)
}
- for _, elt := range n.Elts {
- if kv, ok := elt.(*ast.KeyValueExpr); ok {
- ast.Inspect(kv.Value, visit)
- } else {
- ast.Inspect(elt, visit)
+ for i, elt := range n.Elts {
+ curElt := cur.ChildAt(edge.CompositeLit_Elts, i)
+ if _, ok := elt.(*ast.KeyValueExpr); ok {
+ // skip kv.Key
+ curElt = curElt.ChildAt(edge.KeyValueExpr_Value, -1)
}
+ curElt.Inspect(filter, visit)
}
- return visit(nil) // pop stack, don't descend
+ return false // don't descend
}
}
return true
}
- for _, f := range pkg.Syntax() {
- ast.Inspect(f, visit)
- if len(stack) != 0 {
- panic(stack)
- }
+ for _, pgf := range pkg.CompiledGoFiles() {
+ pgf.Cursor.Inspect(filter, visit)
if !ok {
break
}
@@ -404,11 +400,10 @@ func forEachLexicalRef(pkg *cache.Package, obj types.Object, fn func(id *ast.Ide
}
// enclosingBlock returns the innermost block logically enclosing the
-// specified AST node (an ast.Ident), specified in the form of a path
-// from the root of the file, [file...n].
-func enclosingBlock(info *types.Info, stack []ast.Node) *types.Scope {
- for i := range stack {
- n := stack[len(stack)-1-i]
+// AST node (an ast.Ident), specified as a Cursor.
+func enclosingBlock(info *types.Info, curId cursor.Cursor) *types.Scope {
+ for cur := range curId.Enclosing() {
+ n := cur.Node()
// For some reason, go/types always associates a
// function's scope with its FuncType.
// See comments about scope above.
@@ -472,14 +467,15 @@ func (r *renamer) checkStructField(from *types.Var) {
// This struct is also a named type.
// We must check for direct (non-promoted) field/field
// and method/field conflicts.
- named := r.pkg.TypesInfo().Defs[spec.Name].Type()
- prev, indices, _ := types.LookupFieldOrMethod(named, true, r.pkg.Types(), r.to)
- if len(indices) == 1 {
- r.errorf(from.Pos(), "renaming this field %q to %q",
- from.Name(), r.to)
- r.errorf(prev.Pos(), "\twould conflict with this %s",
- objectKind(prev))
- return // skip checkSelections to avoid redundant errors
+ if tname := r.pkg.TypesInfo().Defs[spec.Name]; tname != nil {
+ prev, indices, _ := types.LookupFieldOrMethod(tname.Type(), true, r.pkg.Types(), r.to)
+ if len(indices) == 1 {
+ r.errorf(from.Pos(), "renaming this field %q to %q",
+ from.Name(), r.to)
+ r.errorf(prev.Pos(), "\twould conflict with this %s",
+ objectKind(prev))
+ return // skip checkSelections to avoid redundant errors
+ }
}
} else {
// This struct is not a named type.
diff --git a/gopls/internal/golang/semtok.go b/gopls/internal/golang/semtok.go
index 121531d8280..f0286ff1fb3 100644
--- a/gopls/internal/golang/semtok.go
+++ b/gopls/internal/golang/semtok.go
@@ -17,6 +17,7 @@ import (
"log"
"path/filepath"
"regexp"
+ "slices"
"strconv"
"strings"
"time"
@@ -210,7 +211,7 @@ func (tv *tokenVisitor) comment(c *ast.Comment, importByName map[string]*types.P
}
pos := c.Pos()
- for _, line := range strings.Split(c.Text, "\n") {
+ for line := range strings.SplitSeq(c.Text, "\n") {
last := 0
for _, idx := range docLinkRegex.FindAllStringSubmatchIndex(line, -1) {
@@ -721,10 +722,8 @@ func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.Type, []semtok.Modifier)
return semtok.TokType, nil
}
case *ast.ValueSpec:
- for _, p := range parent.Names {
- if p == id {
- return semtok.TokVariable, def
- }
+ if slices.Contains(parent.Names, id) {
+ return semtok.TokVariable, def
}
for _, p := range parent.Values {
if p == id {
diff --git a/gopls/internal/golang/type_hierarchy.go b/gopls/internal/golang/type_hierarchy.go
new file mode 100644
index 00000000000..bbcd5325d7b
--- /dev/null
+++ b/gopls/internal/golang/type_hierarchy.go
@@ -0,0 +1,157 @@
+// Copyright 2025 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 golang
+
+import (
+ "context"
+ "fmt"
+ "go/token"
+ "go/types"
+ "slices"
+ "strings"
+ "sync"
+
+ "golang.org/x/tools/gopls/internal/cache"
+ "golang.org/x/tools/gopls/internal/cache/metadata"
+ "golang.org/x/tools/gopls/internal/cache/methodsets"
+ "golang.org/x/tools/gopls/internal/file"
+ "golang.org/x/tools/gopls/internal/protocol"
+)
+
+// Type hierarchy support (using method sets)
+//
+// TODO(adonovan):
+// - Support type hierarchy by signatures (using Kind=Function).
+// As with Implementations by signature matching, needs more UX thought.
+//
+// - Allow methods too (using Kind=Method)? It's not exactly in the
+// spirit of TypeHierarchy but it would be useful and it's easy
+// enough to support.
+//
+// FIXME: fix pkg=command-line-arguments problem with query initiated at "error" in builtins.go
+
+// PrepareTypeHierarchy returns the TypeHierarchyItems for the types at the selected position.
+func PrepareTypeHierarchy(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.TypeHierarchyItem, error) {
+ pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+ if err != nil {
+ return nil, err
+ }
+ pos, err := pgf.PositionPos(pp)
+ if err != nil {
+ return nil, err
+ }
+
+ // For now, we require that the selection be a type name.
+ _, obj, _ := referencedObject(pkg, pgf, pos)
+ if obj == nil {
+ return nil, fmt.Errorf("not a symbol")
+ }
+ tname, ok := obj.(*types.TypeName)
+ if !ok {
+ return nil, fmt.Errorf("not a type name")
+ }
+
+ // Find declaration.
+ var declLoc protocol.Location
+ if isBuiltin(obj) {
+ pgf, id, err := builtinDecl(ctx, snapshot, obj)
+ if err != nil {
+ return nil, err
+ }
+ declLoc, err = pgf.NodeLocation(id)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ declLoc, err = mapPosition(ctx, pkg.FileSet(), snapshot, tname.Pos(), tname.Pos()+token.Pos(len(tname.Name())))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ pkgpath := "builtin"
+ if tname.Pkg() != nil {
+ pkgpath = tname.Pkg().Path()
+ }
+
+ return []protocol.TypeHierarchyItem{{
+ Name: tname.Name(),
+ Kind: cond(types.IsInterface(tname.Type()), protocol.Interface, protocol.Class),
+ Detail: pkgpath,
+ URI: declLoc.URI,
+ Range: declLoc.Range, // (in theory this should be the entire declaration)
+ SelectionRange: declLoc.Range,
+ }}, nil
+}
+
+// Subtypes reports information about subtypes of the selected type.
+func Subtypes(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, item protocol.TypeHierarchyItem) ([]protocol.TypeHierarchyItem, error) {
+ return relatedTypes(ctx, snapshot, fh, item, methodsets.Subtype)
+}
+
+// Subtypes reports information about supertypes of the selected type.
+func Supertypes(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, item protocol.TypeHierarchyItem) ([]protocol.TypeHierarchyItem, error) {
+ return relatedTypes(ctx, snapshot, fh, item, methodsets.Supertype)
+}
+
+// relatedTypes is the common implementation of {Super,Sub}types.
+func relatedTypes(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, item protocol.TypeHierarchyItem, rel methodsets.TypeRelation) ([]protocol.TypeHierarchyItem, error) {
+ pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+ if err != nil {
+ return nil, err
+ }
+ pos, err := pgf.PositionPos(item.Range.Start)
+ if err != nil {
+ return nil, err
+ }
+
+ var (
+ itemsMu sync.Mutex
+ items []protocol.TypeHierarchyItem
+ )
+ err = implementationsMsets(ctx, snapshot, pkg, pgf, pos, rel, func(pkgpath metadata.PackagePath, name string, abstract bool, loc protocol.Location) {
+ if pkgpath == "" {
+ pkgpath = "builtin"
+ }
+
+ itemsMu.Lock()
+ defer itemsMu.Unlock()
+ items = append(items, protocol.TypeHierarchyItem{
+ Name: name,
+ Kind: cond(abstract, protocol.Interface, protocol.Class),
+ Detail: string(pkgpath),
+ URI: loc.URI,
+ Range: loc.Range, // (in theory this should be the entire declaration)
+ SelectionRange: loc.Range,
+ })
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Sort by (package, name, URI, range) then
+ // de-duplicate based on the same 4-tuple
+ cmp := func(x, y protocol.TypeHierarchyItem) int {
+ if d := strings.Compare(x.Detail, y.Detail); d != 0 {
+ // Rank the original item's package first.
+ if d := boolCompare(x.Detail == item.Detail, y.Detail == item.Detail); d != 0 {
+ return -d
+ }
+ return d
+ }
+ if d := strings.Compare(x.Name, y.Name); d != 0 {
+ return d
+ }
+ if d := strings.Compare(string(x.URI), string(y.URI)); d != 0 {
+ return d
+ }
+ return protocol.CompareRange(x.SelectionRange, y.Range)
+ }
+ slices.SortFunc(items, cmp)
+ eq := func(x, y protocol.TypeHierarchyItem) bool { return cmp(x, y) == 0 }
+ items = slices.CompactFunc(items, eq)
+
+ return items, nil
+}
diff --git a/gopls/internal/golang/undeclared.go b/gopls/internal/golang/undeclared.go
index 9df8e2bfd2e..515da9bd891 100644
--- a/gopls/internal/golang/undeclared.go
+++ b/gopls/internal/golang/undeclared.go
@@ -251,7 +251,7 @@ func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package,
// results is used as an argument
case *types.Tuple:
n := t.Len()
- for i := 0; i < n; i++ {
+ for i := range n {
name := typeToArgName(t.At(i).Type())
nameCounts[name]++
diff --git a/gopls/internal/golang/util.go b/gopls/internal/golang/util.go
index b13056e02b9..5c54bfcf751 100644
--- a/gopls/internal/golang/util.go
+++ b/gopls/internal/golang/util.go
@@ -11,6 +11,7 @@ import (
"go/token"
"go/types"
"regexp"
+ "slices"
"strings"
"unicode"
@@ -89,10 +90,8 @@ func findFileInDeps(s metadata.Source, mp *metadata.Package, uri protocol.Docume
return nil
}
seen[mp.ID] = true
- for _, cgf := range mp.CompiledGoFiles {
- if cgf == uri {
- return mp
- }
+ if slices.Contains(mp.CompiledGoFiles, uri) {
+ return mp
}
for _, dep := range mp.DepsByPkgPath {
mp := s.Metadata(dep)
diff --git a/gopls/internal/golang/workspace_symbol.go b/gopls/internal/golang/workspace_symbol.go
index 91c5ee22925..1a0819b4d52 100644
--- a/gopls/internal/golang/workspace_symbol.go
+++ b/gopls/internal/golang/workspace_symbol.go
@@ -389,7 +389,7 @@ func collectSymbols(ctx context.Context, snapshots []*cache.Snapshot, matcherTyp
// which we merge at the end.
nmatchers := runtime.GOMAXPROCS(-1) // matching is CPU bound
results := make(chan *symbolStore)
- for i := 0; i < nmatchers; i++ {
+ for i := range nmatchers {
go func(i int) {
matcher := buildMatcher(matcherType, query)
store := new(symbolStore)
@@ -403,7 +403,7 @@ func collectSymbols(ctx context.Context, snapshots []*cache.Snapshot, matcherTyp
// Gather and merge results as they arrive.
var unified symbolStore
- for i := 0; i < nmatchers; i++ {
+ for range nmatchers {
store := <-results
for _, syms := range store.res {
if syms != nil {
diff --git a/gopls/internal/licenses/gen-licenses.sh b/gopls/internal/licenses/gen-licenses.sh
index a39f87ce845..b615e566324 100755
--- a/gopls/internal/licenses/gen-licenses.sh
+++ b/gopls/internal/licenses/gen-licenses.sh
@@ -25,9 +25,9 @@ END
# are known to have the same license.
mods=$(go list -deps -f '{{with .Module}}{{.Path}}{{end}}' golang.org/x/tools/gopls | sort -u | grep -v golang.org)
for mod in $mods; do
- # Find the license file, either LICENSE or COPYING, and add it to the result.
+ # Find the license file, either LICENSE, COPYING, or LICENSE.md and add it to the result.
dir=$(go list -m -f {{.Dir}} $mod)
- license=$(ls -1 $dir | grep -E -i '^(LICENSE|COPYING)$')
+ license=$(ls -1 $dir | grep -E -i '^(LICENSE|LICENSE.md|COPYING)?$')
echo "-- $mod $license --" >> $tempfile
echo >> $tempfile
sed 's/^-- / &/' $dir/$license >> $tempfile
diff --git a/gopls/internal/licenses/licenses.go b/gopls/internal/licenses/licenses.go
index e8c5ba9c691..ee73aba2e41 100644
--- a/gopls/internal/licenses/licenses.go
+++ b/gopls/internal/licenses/licenses.go
@@ -30,6 +30,122 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+-- github.com/fatih/camelcase LICENSE.md --
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Fatih Arslan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- github.com/fatih/gomodifytags LICENSE --
+
+Copyright (c) 2017, Fatih Arslan
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of gomodifytags nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-- github.com/fatih/structtag LICENSE --
+
+Copyright (c) 2017, Fatih Arslan
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of structtag nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This software includes some portions from Go. Go is used under the terms of the
+BSD like license.
+
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The Go gopher was designed by Renee French. http://reneefrench.blogspot.com/ The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: https://blog.golang.org/gopher
+
-- github.com/google/go-cmp LICENSE --
Copyright (c) 2017 The Go Authors. All rights reserved.
diff --git a/gopls/internal/lsprpc/binder_test.go b/gopls/internal/lsprpc/binder_test.go
index 07a8b2cdf99..7072529d1c6 100644
--- a/gopls/internal/lsprpc/binder_test.go
+++ b/gopls/internal/lsprpc/binder_test.go
@@ -105,7 +105,7 @@ func (e *TestEnv) dial(ctx context.Context, t *testing.T, dialer jsonrpc2_v2.Dia
l, _ := e.serve(ctx, t, NewForwardBinder(dialer))
dialer = l.Dialer()
}
- conn, err := jsonrpc2_v2.Dial(ctx, dialer, client)
+ conn, err := jsonrpc2_v2.Dial(ctx, dialer, client, nil)
if err != nil {
t.Fatal(err)
}
diff --git a/gopls/internal/lsprpc/dialer.go b/gopls/internal/lsprpc/dialer.go
index a5f038df9f1..b9aabe4947b 100644
--- a/gopls/internal/lsprpc/dialer.go
+++ b/gopls/internal/lsprpc/dialer.go
@@ -97,7 +97,7 @@ func (d *autoDialer) dialNet(ctx context.Context) (net.Conn, error) {
const retries = 5
// It can take some time for the newly started server to bind to our address,
// so we retry for a bit.
- for retry := 0; retry < retries; retry++ {
+ for retry := range retries {
startDial := time.Now()
netConn, err = net.DialTimeout(d.network, d.addr, dialTimeout)
if err == nil {
diff --git a/gopls/internal/lsprpc/export_test.go b/gopls/internal/lsprpc/export_test.go
index 8cbdecc98a2..1caf22415cb 100644
--- a/gopls/internal/lsprpc/export_test.go
+++ b/gopls/internal/lsprpc/export_test.go
@@ -34,13 +34,8 @@ func (c *Canceler) Preempt(ctx context.Context, req *jsonrpc2_v2.Request) (any,
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
return nil, fmt.Errorf("%w: %v", jsonrpc2_v2.ErrParse, err)
}
- var id jsonrpc2_v2.ID
- switch raw := params.ID.(type) {
- case float64:
- id = jsonrpc2_v2.Int64ID(int64(raw))
- case string:
- id = jsonrpc2_v2.StringID(raw)
- default:
+ id, err := jsonrpc2_v2.MakeID(params.ID)
+ if err != nil {
return nil, fmt.Errorf("%w: invalid ID type %T", jsonrpc2_v2.ErrParse, params.ID)
}
c.Conn.Cancel(id)
@@ -62,7 +57,7 @@ func (b *ForwardBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection)
client := protocol.ClientDispatcherV2(conn)
clientBinder := NewClientBinder(func(context.Context, protocol.Server) protocol.Client { return client })
- serverConn, err := jsonrpc2_v2.Dial(context.Background(), b.dialer, clientBinder)
+ serverConn, err := jsonrpc2_v2.Dial(context.Background(), b.dialer, clientBinder, nil)
if err != nil {
return jsonrpc2_v2.ConnectionOptions{
Handler: jsonrpc2_v2.HandlerFunc(func(context.Context, *jsonrpc2_v2.Request) (any, error) {
diff --git a/gopls/internal/lsprpc/lsprpc_test.go b/gopls/internal/lsprpc/lsprpc_test.go
index eda00b28c7a..c8f0267cc3c 100644
--- a/gopls/internal/lsprpc/lsprpc_test.go
+++ b/gopls/internal/lsprpc/lsprpc_test.go
@@ -52,8 +52,7 @@ func (s PingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDoc
}
func TestClientLogging(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
server := PingServer{}
client := FakeClient{Logs: make(chan string, 10)}
@@ -212,8 +211,7 @@ func TestDebugInfoLifecycle(t *testing.T) {
}
}()
- baseCtx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ baseCtx := t.Context()
clientCtx := debug.WithInstance(baseCtx)
serverCtx := debug.WithInstance(baseCtx)
diff --git a/gopls/internal/mod/diagnostics.go b/gopls/internal/mod/diagnostics.go
index 8ad1ece05e7..52f3704ed0f 100644
--- a/gopls/internal/mod/diagnostics.go
+++ b/gopls/internal/mod/diagnostics.go
@@ -69,7 +69,6 @@ func collectDiagnostics(ctx context.Context, snapshot *cache.Snapshot, diagFn fu
reports := make(map[protocol.DocumentURI][]*cache.Diagnostic)
for _, uri := range snapshot.View().ModFiles() {
- uri := uri
g.Go(func() error {
fh, err := snapshot.ReadFile(ctx, uri)
if err != nil {
diff --git a/gopls/internal/mod/hover.go b/gopls/internal/mod/hover.go
index 458c5ce67d5..b9b026674fa 100644
--- a/gopls/internal/mod/hover.go
+++ b/gopls/internal/mod/hover.go
@@ -8,10 +8,12 @@ import (
"bytes"
"context"
"fmt"
+ "slices"
"sort"
"strings"
"golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
"golang.org/x/mod/semver"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/file"
@@ -24,16 +26,8 @@ import (
)
func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) {
- var found bool
- for _, uri := range snapshot.View().ModFiles() {
- if fh.URI() == uri {
- found = true
- break
- }
- }
-
// We only provide hover information for the view's go.mod files.
- if !found {
+ if !slices.Contains(snapshot.View().ModFiles(), fh.URI()) {
return nil, nil
}
@@ -116,7 +110,7 @@ func hoverOnRequireStatement(ctx context.Context, pm *cache.ParsedModule, offset
options := snapshot.Options()
isPrivate := snapshot.IsGoPrivatePath(req.Mod.Path)
header := formatHeader(req.Mod.Path, options)
- explanation = formatExplanation(explanation, req, options, isPrivate)
+ explanation = formatExplanation(explanation, pm.ReplaceMap, req, options, isPrivate)
vulns := formatVulnerabilities(affecting, nonaffecting, osvs, options, fromGovulncheck)
return &protocol.Hover{
@@ -327,7 +321,7 @@ func vulnerablePkgsInfo(findings []*govulncheck.Finding, useMarkdown bool) strin
return b.String()
}
-func formatExplanation(text string, req *modfile.Require, options *settings.Options, isPrivate bool) string {
+func formatExplanation(text string, replaceMap map[module.Version]module.Version, req *modfile.Require, options *settings.Options, isPrivate bool) string {
text = strings.TrimSuffix(text, "\n")
splt := strings.Split(text, "\n")
length := len(splt)
@@ -348,7 +342,17 @@ func formatExplanation(text string, req *modfile.Require, options *settings.Opti
if !isPrivate && options.PreferredContentFormat == protocol.Markdown {
target := imp
if strings.ToLower(options.LinkTarget) == "pkg.go.dev" {
- target = strings.Replace(target, req.Mod.Path, req.Mod.String(), 1)
+ mod := req.Mod
+ // respect the repalcement when constructing a module link.
+ if m, ok := replaceMap[req.Mod]; ok {
+ // Have: 'replace A v1.2.3 => A vx.x.x' or 'replace A v1.2.3 => B vx.x.x'.
+ mod = m
+ } else if m, ok := replaceMap[module.Version{Path: req.Mod.Path}]; ok &&
+ !modfile.IsDirectoryPath(m.Path) { // exclude local replacement.
+ // Have: 'replace A => A vx.x.x' or 'replace A => B vx.x.x'.
+ mod = m
+ }
+ target = strings.Replace(target, req.Mod.Path, mod.String(), 1)
}
reference = fmt.Sprintf("[%s](%s)", imp, cache.BuildLink(options.LinkTarget, target, ""))
}
diff --git a/gopls/internal/progress/progress_test.go b/gopls/internal/progress/progress_test.go
index 642103ae025..687f99ba4a1 100644
--- a/gopls/internal/progress/progress_test.go
+++ b/gopls/internal/progress/progress_test.go
@@ -107,7 +107,6 @@ func TestProgressTracker_Reporting(t *testing.T) {
wantEnded: 1,
},
} {
- test := test
t.Run(test.name, func(t *testing.T) {
ctx, tracker, client := setup()
ctx, cancel := context.WithCancel(ctx)
diff --git a/gopls/internal/protocol/command/command_gen.go b/gopls/internal/protocol/command/command_gen.go
index c9b18a40cb8..b6c12e4b50c 100644
--- a/gopls/internal/protocol/command/command_gen.go
+++ b/gopls/internal/protocol/command/command_gen.go
@@ -46,6 +46,7 @@ const (
ListKnownPackages Command = "gopls.list_known_packages"
MaybePromptForTelemetry Command = "gopls.maybe_prompt_for_telemetry"
MemStats Command = "gopls.mem_stats"
+ ModifyTags Command = "gopls.modify_tags"
Modules Command = "gopls.modules"
PackageSymbols Command = "gopls.package_symbols"
Packages Command = "gopls.packages"
@@ -91,6 +92,7 @@ var Commands = []Command{
ListKnownPackages,
MaybePromptForTelemetry,
MemStats,
+ ModifyTags,
Modules,
PackageSymbols,
Packages,
@@ -242,6 +244,12 @@ func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Inte
return nil, s.MaybePromptForTelemetry(ctx)
case MemStats:
return s.MemStats(ctx)
+ case ModifyTags:
+ var a0 ModifyTagsArgs
+ if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
+ return nil, err
+ }
+ return nil, s.ModifyTags(ctx, a0)
case Modules:
var a0 ModulesArgs
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
@@ -530,6 +538,14 @@ func NewMemStatsCommand(title string) *protocol.Command {
}
}
+func NewModifyTagsCommand(title string, a0 ModifyTagsArgs) *protocol.Command {
+ return &protocol.Command{
+ Title: title,
+ Command: ModifyTags.String(),
+ Arguments: MustMarshalArgs(a0),
+ }
+}
+
func NewModulesCommand(title string, a0 ModulesArgs) *protocol.Command {
return &protocol.Command{
Title: title,
diff --git a/gopls/internal/protocol/command/commandmeta/meta.go b/gopls/internal/protocol/command/commandmeta/meta.go
index f147898e192..7c3a3acc12f 100644
--- a/gopls/internal/protocol/command/commandmeta/meta.go
+++ b/gopls/internal/protocol/command/commandmeta/meta.go
@@ -224,10 +224,7 @@ func lspName(methodName string) string {
func splitCamel(s string) []string {
var words []string
for len(s) > 0 {
- last := strings.LastIndexFunc(s, unicode.IsUpper)
- if last < 0 {
- last = 0
- }
+ last := max(strings.LastIndexFunc(s, unicode.IsUpper), 0)
if last == len(s)-1 {
// Group initialisms as a single word.
last = 1 + strings.LastIndexFunc(s[:last], func(r rune) bool { return !unicode.IsUpper(r) })
diff --git a/gopls/internal/protocol/command/interface.go b/gopls/internal/protocol/command/interface.go
index 34adf59b38e..01d41dec473 100644
--- a/gopls/internal/protocol/command/interface.go
+++ b/gopls/internal/protocol/command/interface.go
@@ -297,6 +297,9 @@ type Interface interface {
// PackageSymbols: Return information about symbols in the given file's package.
PackageSymbols(context.Context, PackageSymbolsArgs) (PackageSymbolsResult, error)
+
+ // ModifyTags: Add or remove struct tags on a given node.
+ ModifyTags(context.Context, ModifyTagsArgs) error
}
type RunTestsArgs struct {
@@ -830,3 +833,19 @@ type PackageSymbol struct {
// Index of this symbol's file in PackageSymbolsResult.Files
File int `json:"file,omitempty"`
}
+
+// ModifyTagsArgs holds variables that determine how struct tags are modified.
+type ModifyTagsArgs struct {
+ URI protocol.DocumentURI // uri of the file to be modified
+ Range protocol.Range // range in the file for where to modify struct tags
+ Add string // comma-separated list of tags to add; i.e. "json,xml"
+ AddOptions string // comma-separated list of options to add, per tag; i.e. "json=omitempty"
+ Remove string // comma-separated list of tags to remove
+ RemoveOptions string // comma-separated list of options to remove
+ Clear bool // if set, clear all tags. tags are cleared before any new tags are added
+ ClearOptions bool // if set, clear all tag options; options are cleared before any new options are added
+ Overwrite bool // if set, replace existing tags when adding
+ SkipUnexportedFields bool // if set, do not modify tags on unexported struct fields
+ Transform string // transform rule for adding tags; i.e. "snakecase"
+ ValueFormat string // format for the tag's value, after transformation; for example "column:{field}"
+}
diff --git a/gopls/internal/protocol/edits.go b/gopls/internal/protocol/edits.go
index 5f70c4efdb5..c5d3592a8ee 100644
--- a/gopls/internal/protocol/edits.go
+++ b/gopls/internal/protocol/edits.go
@@ -6,6 +6,7 @@ package protocol
import (
"fmt"
+ "slices"
"golang.org/x/tools/internal/diff"
)
@@ -16,7 +17,7 @@ func EditsFromDiffEdits(m *Mapper, edits []diff.Edit) ([]TextEdit, error) {
// LSP doesn't require TextEditArray to be sorted:
// this is the receiver's concern. But govim, and perhaps
// other clients have historically relied on the order.
- edits = append([]diff.Edit(nil), edits...)
+ edits = slices.Clone(edits)
diff.SortEdits(edits)
result := make([]TextEdit, len(edits))
diff --git a/gopls/internal/protocol/generate/generate.go b/gopls/internal/protocol/generate/generate.go
index 9c7009113ab..fef8ef417eb 100644
--- a/gopls/internal/protocol/generate/generate.go
+++ b/gopls/internal/protocol/generate/generate.go
@@ -32,7 +32,7 @@ func generateDoc(out *bytes.Buffer, doc string) {
return
}
var list bool
- for _, line := range strings.Split(doc, "\n") {
+ for line := range strings.SplitSeq(doc, "\n") {
// Lists in metaModel.json start with a dash.
// To make a go doc list they have to be preceded
// by a blank line, and indented.
diff --git a/gopls/internal/protocol/json_test.go b/gopls/internal/protocol/json_test.go
index 9aac110fa3b..2c03095a84c 100644
--- a/gopls/internal/protocol/json_test.go
+++ b/gopls/internal/protocol/json_test.go
@@ -103,15 +103,9 @@ func tryChange(start, end int, repl string) error {
var p, q protocol.ParamInitialize
mod := input[:start] + repl + input[end:]
excerpt := func() (string, string) {
- a := start - 5
- if a < 0 {
- a = 0
- }
- b := end + 5
- if b > len(input) {
- // trusting repl to be no longer than what it replaces
- b = len(input)
- }
+ a := max(start-5, 0)
+ // trusting repl to be no longer than what it replaces
+ b := min(end+5, len(input))
ma := input[a:b]
mb := mod[a:b]
return ma, mb
diff --git a/gopls/internal/protocol/semtok/semtok.go b/gopls/internal/protocol/semtok/semtok.go
index 6b05b8bb5e2..86332d37e1a 100644
--- a/gopls/internal/protocol/semtok/semtok.go
+++ b/gopls/internal/protocol/semtok/semtok.go
@@ -173,7 +173,7 @@ func Encode(
x := make([]uint32, 5*len(tokens))
var j int
var last Token
- for i := 0; i < len(tokens); i++ {
+ for i := range tokens {
item := tokens[i]
typ, ok := typeMap[item.Type]
if !ok {
diff --git a/gopls/internal/protocol/uri.go b/gopls/internal/protocol/uri.go
index 4105bd041f8..491d767805f 100644
--- a/gopls/internal/protocol/uri.go
+++ b/gopls/internal/protocol/uri.go
@@ -67,6 +67,11 @@ func (uri *DocumentURI) UnmarshalText(data []byte) (err error) {
return
}
+// Clean returns the cleaned uri by triggering filepath.Clean underlying.
+func Clean(uri DocumentURI) DocumentURI {
+ return URIFromPath(filepath.Clean(uri.Path()))
+}
+
// Path returns the file path for the given URI.
//
// DocumentURI("").Path() returns the empty string.
diff --git a/gopls/internal/server/call_hierarchy.go b/gopls/internal/server/call_hierarchy.go
index 758a4628948..1887767250c 100644
--- a/gopls/internal/server/call_hierarchy.go
+++ b/gopls/internal/server/call_hierarchy.go
@@ -22,10 +22,11 @@ func (s *server) PrepareCallHierarchy(ctx context.Context, params *protocol.Call
return nil, err
}
defer release()
- if snapshot.FileKind(fh) != file.Go {
- return nil, nil // empty result
+ switch snapshot.FileKind(fh) {
+ case file.Go:
+ return golang.PrepareCallHierarchy(ctx, snapshot, fh, params.Position)
}
- return golang.PrepareCallHierarchy(ctx, snapshot, fh, params.Position)
+ return nil, nil // empty result
}
func (s *server) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) {
@@ -37,10 +38,11 @@ func (s *server) IncomingCalls(ctx context.Context, params *protocol.CallHierarc
return nil, err
}
defer release()
- if snapshot.FileKind(fh) != file.Go {
- return nil, nil // empty result
+ switch snapshot.FileKind(fh) {
+ case file.Go:
+ return golang.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start)
}
- return golang.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start)
+ return nil, nil // empty result
}
func (s *server) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) {
@@ -52,8 +54,9 @@ func (s *server) OutgoingCalls(ctx context.Context, params *protocol.CallHierarc
return nil, err
}
defer release()
- if snapshot.FileKind(fh) != file.Go {
- return nil, nil // empty result
+ switch snapshot.FileKind(fh) {
+ case file.Go:
+ return golang.OutgoingCalls(ctx, snapshot, fh, params.Item.Range.Start)
}
- return golang.OutgoingCalls(ctx, snapshot, fh, params.Item.Range.Start)
+ return nil, nil // empty result
}
diff --git a/gopls/internal/server/code_action.go b/gopls/internal/server/code_action.go
index 4617fad5de7..9fa2bf54459 100644
--- a/gopls/internal/server/code_action.go
+++ b/gopls/internal/server/code_action.go
@@ -8,7 +8,6 @@ import (
"context"
"fmt"
"slices"
- "sort"
"strings"
"golang.org/x/tools/gopls/internal/cache"
@@ -354,9 +353,7 @@ func (s *server) getSupportedCodeActions() []protocol.CodeActionKind {
for kind := range allCodeActionKinds {
result = append(result, kind)
}
- sort.Slice(result, func(i, j int) bool {
- return result[i] < result[j]
- })
+ slices.Sort(result)
return result
}
diff --git a/gopls/internal/server/command.go b/gopls/internal/server/command.go
index ca8177530e5..a3345d33a1d 100644
--- a/gopls/internal/server/command.go
+++ b/gopls/internal/server/command.go
@@ -12,6 +12,7 @@ import (
"fmt"
"io"
"log"
+ "maps"
"os"
"path/filepath"
"regexp"
@@ -22,6 +23,7 @@ import (
"strings"
"sync"
+ "github.com/fatih/gomodifytags/modifytags"
"golang.org/x/mod/modfile"
"golang.org/x/telemetry/counter"
"golang.org/x/tools/go/ast/astutil"
@@ -59,14 +61,7 @@ func (s *server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCom
defer work.End(ctx, "Done.")
}
- var found bool
- for _, name := range s.Options().SupportedCommands {
- if name == params.Command {
- found = true
- break
- }
- }
- if !found {
+ if !slices.Contains(s.Options().SupportedCommands, params.Command) {
return nil, fmt.Errorf("%s is not a supported command", params.Command)
}
@@ -1202,9 +1197,7 @@ func (c *commandHandler) FetchVulncheckResult(ctx context.Context, arg command.U
}
}
// Overwrite if there is any govulncheck-based result.
- for modfile, result := range deps.snapshot.Vulnerabilities() {
- ret[modfile] = result
- }
+ maps.Copy(ret, deps.snapshot.Vulnerabilities())
return nil
})
return ret, err
@@ -1671,7 +1664,6 @@ func (c *commandHandler) DiagnoseFiles(ctx context.Context, args command.Diagnos
var wg sync.WaitGroup
for snapshot := range snapshots {
- snapshot := snapshot
wg.Add(1)
go func() {
defer wg.Done()
@@ -1764,3 +1756,88 @@ func (c *commandHandler) PackageSymbols(ctx context.Context, args command.Packag
return result, err
}
+
+// optionsStringToMap transforms comma-separated options of the form
+// "foo=bar,baz=quux" to a go map. Returns nil if any options are malformed.
+func optionsStringToMap(options string) (map[string][]string, error) {
+ optionsMap := make(map[string][]string)
+ for item := range strings.SplitSeq(options, ",") {
+ key, option, found := strings.Cut(item, "=")
+ if !found {
+ return nil, fmt.Errorf("invalid option %q", item)
+ }
+ optionsMap[key] = append(optionsMap[key], option)
+ }
+ return optionsMap, nil
+}
+
+func (c *commandHandler) ModifyTags(ctx context.Context, args command.ModifyTagsArgs) error {
+ return c.run(ctx, commandConfig{
+ progress: "Modifying tags",
+ forURI: args.URI,
+ }, func(ctx context.Context, deps commandDeps) error {
+ m := &modifytags.Modification{
+ Clear: args.Clear,
+ ClearOptions: args.ClearOptions,
+ ValueFormat: args.ValueFormat,
+ Overwrite: args.Overwrite,
+ }
+
+ transform, err := parseTransform(args.Transform)
+ if err != nil {
+ return err
+ }
+ m.Transform = transform
+
+ if args.Add != "" {
+ m.Add = strings.Split(args.Add, ",")
+ }
+ if args.AddOptions != "" {
+ if options, err := optionsStringToMap(args.AddOptions); err != nil {
+ return err
+ } else {
+ m.AddOptions = options
+ }
+ }
+ if args.Remove != "" {
+ m.Remove = strings.Split(args.Remove, ",")
+ }
+ if args.RemoveOptions != "" {
+ if options, err := optionsStringToMap(args.RemoveOptions); err != nil {
+ return err
+ } else {
+ m.RemoveOptions = options
+ }
+ }
+ fh, err := deps.snapshot.ReadFile(ctx, args.URI)
+ if err != nil {
+ return err
+ }
+ changes, err := golang.ModifyTags(ctx, deps.snapshot, fh, args, m)
+ if err != nil {
+ return err
+ }
+ return applyChanges(ctx, c.s.client, changes)
+ })
+}
+
+func parseTransform(input string) (modifytags.Transform, error) {
+ switch input {
+ case "camelcase":
+ return modifytags.CamelCase, nil
+ case "lispcase":
+ return modifytags.LispCase, nil
+ case "pascalcase":
+ return modifytags.PascalCase, nil
+ case "titlecase":
+ return modifytags.TitleCase, nil
+ case "keep":
+ return modifytags.Keep, nil
+ case "":
+ fallthrough
+ case "snakecase":
+ return modifytags.SnakeCase, nil
+ default:
+ return modifytags.SnakeCase, fmt.Errorf("invalid Transform value")
+ }
+}
diff --git a/gopls/internal/server/diagnostics.go b/gopls/internal/server/diagnostics.go
index 92ca54e226a..dbffc58fd99 100644
--- a/gopls/internal/server/diagnostics.go
+++ b/gopls/internal/server/diagnostics.go
@@ -128,7 +128,6 @@ func (s *server) diagnoseChangedViews(ctx context.Context, modID uint64, lastCha
// Diagnose views concurrently.
var wg sync.WaitGroup
for _, v := range needsDiagnosis {
- v := v
snapshot, release, err := v.Snapshot()
if err != nil {
s.modificationMu.Lock()
diff --git a/gopls/internal/server/general.go b/gopls/internal/server/general.go
index 5e02b832747..6ce1f788dba 100644
--- a/gopls/internal/server/general.go
+++ b/gopls/internal/server/general.go
@@ -184,6 +184,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitializ
IncludeText: false,
},
},
+ TypeHierarchyProvider: &protocol.Or_ServerCapabilities_typeHierarchyProvider{Value: true},
Workspace: &protocol.WorkspaceOptions{
WorkspaceFolders: &protocol.WorkspaceFolders5Gn{
Supported: true,
@@ -312,10 +313,15 @@ func (s *server) addFolders(ctx context.Context, folders []protocol.WorkspaceFol
// but the list can grow over time.
var filtered []protocol.WorkspaceFolder
for _, f := range folders {
- if _, err := protocol.ParseDocumentURI(f.URI); err != nil {
+ uri, err := protocol.ParseDocumentURI(f.URI)
+ if err != nil {
debuglog.Warning.Logf(ctx, "skip adding virtual folder %q - invalid folder URI: %v", f.Name, err)
continue
}
+ if s.session.HasView(uri) {
+ debuglog.Warning.Logf(ctx, "skip adding the already added folder %q - its view has been created before", f.Name)
+ continue
+ }
filtered = append(filtered, f)
}
folders = filtered
diff --git a/gopls/internal/server/link.go b/gopls/internal/server/link.go
index cf475ca90c9..75c717dbe8e 100644
--- a/gopls/internal/server/link.go
+++ b/gopls/internal/server/link.go
@@ -11,11 +11,13 @@ import (
"go/ast"
"go/token"
"net/url"
+ "path/filepath"
"regexp"
"strings"
"sync"
"golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/cache/metadata"
"golang.org/x/tools/gopls/internal/cache/parsego"
@@ -59,6 +61,30 @@ func modLinks(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]
}
var links []protocol.DocumentLink
+ for _, rep := range pm.File.Replace {
+ if modfile.IsDirectoryPath(rep.New.Path) {
+ // Have local replacement, such as 'replace A => ../'.
+ dep := []byte(rep.New.Path)
+ start, end := rep.Syntax.Start.Byte, rep.Syntax.End.Byte
+ i := bytes.Index(pm.Mapper.Content[start:end], dep)
+ if i < 0 {
+ continue
+ }
+ path := rep.New.Path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(fh.URI().DirPath(), path)
+ }
+ // jump to the go.mod file of replaced module.
+ path = filepath.Join(filepath.Clean(path), "go.mod")
+ l, err := toProtocolLink(pm.Mapper, protocol.URIFromPath(path).Path(), start+i, start+i+len(dep))
+ if err != nil {
+ return nil, err
+ }
+ links = append(links, l)
+ continue
+ }
+ }
+
for _, req := range pm.File.Require {
if req.Syntax == nil {
continue
@@ -73,9 +99,21 @@ func modLinks(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]
if i == -1 {
continue
}
+
+ mod := req.Mod
+ // respect the repalcement when constructing a module link.
+ if m, ok := pm.ReplaceMap[req.Mod]; ok {
+ // Have: 'replace A v1.2.3 => A vx.x.x' or 'replace A v1.2.3 => B vx.x.x'.
+ mod = m
+ } else if m, ok := pm.ReplaceMap[module.Version{Path: req.Mod.Path}]; ok &&
+ !modfile.IsDirectoryPath(m.Path) { // exclude local replacement.
+ // Have: 'replace A => A vx.x.x' or 'replace A => B vx.x.x'.
+ mod = m
+ }
+
// Shift the start position to the location of the
// dependency within the require statement.
- target := cache.BuildLink(snapshot.Options().LinkTarget, "mod/"+req.Mod.String(), "")
+ target := cache.BuildLink(snapshot.Options().LinkTarget, "mod/"+mod.String(), "")
l, err := toProtocolLink(pm.Mapper, target, start+i, start+i+len(dep))
if err != nil {
return nil, err
@@ -142,8 +180,8 @@ func goLinks(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]p
urlPath := string(importPath)
// For pkg.go.dev, append module version suffix to package import path.
- if mp := snapshot.Metadata(depsByImpPath[importPath]); mp != nil && mp.Module != nil && mp.Module.Path != "" && mp.Module.Version != "" {
- urlPath = strings.Replace(urlPath, mp.Module.Path, mp.Module.Path+"@"+mp.Module.Version, 1)
+ if mp := snapshot.Metadata(depsByImpPath[importPath]); mp != nil && mp.Module != nil && cache.ResolvedPath(mp.Module) != "" && cache.ResolvedVersion(mp.Module) != "" {
+ urlPath = strings.Replace(urlPath, mp.Module.Path, cache.ResolvedString(mp.Module), 1)
}
start, end, err := safetoken.Offsets(pgf.Tok, imp.Path.Pos(), imp.Path.End())
diff --git a/gopls/internal/server/prompt.go b/gopls/internal/server/prompt.go
index 37f591487a6..f8895358942 100644
--- a/gopls/internal/server/prompt.go
+++ b/gopls/internal/server/prompt.go
@@ -283,7 +283,7 @@ func (s *server) maybePromptForTelemetry(ctx context.Context, enabled bool) {
attempts++
}
- pendingContent := []byte(fmt.Sprintf("%s %d %d %d", state, attempts, creationTime, token))
+ pendingContent := fmt.Appendf(nil, "%s %d %d %d", state, attempts, creationTime, token)
if err := os.WriteFile(promptFile, pendingContent, 0666); err != nil {
errorf("writing pending state: %v", err)
return
@@ -351,7 +351,7 @@ Would you like to enable Go telemetry?
message(protocol.Error, fmt.Sprintf("Unrecognized response %q", item.Title))
}
}
- resultContent := []byte(fmt.Sprintf("%s %d %d %d", result, attempts, creationTime, token))
+ resultContent := fmt.Appendf(nil, "%s %d %d %d", result, attempts, creationTime, token)
if err := os.WriteFile(promptFile, resultContent, 0666); err != nil {
errorf("error writing result state to prompt file: %v", err)
}
diff --git a/gopls/internal/server/prompt_test.go b/gopls/internal/server/prompt_test.go
index f4484cb6437..6af5b98eab7 100644
--- a/gopls/internal/server/prompt_test.go
+++ b/gopls/internal/server/prompt_test.go
@@ -27,7 +27,6 @@ func TestAcquireFileLock(t *testing.T) {
var wg sync.WaitGroup
for i := range releasers {
- i := i
wg.Add(1)
go func() {
defer wg.Done()
diff --git a/gopls/internal/server/type_hierarchy.go b/gopls/internal/server/type_hierarchy.go
new file mode 100644
index 00000000000..5f40ed3c0c2
--- /dev/null
+++ b/gopls/internal/server/type_hierarchy.go
@@ -0,0 +1,63 @@
+// Copyright 2025 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 server
+
+import (
+ "context"
+ "fmt"
+
+ "golang.org/x/tools/gopls/internal/file"
+ "golang.org/x/tools/gopls/internal/golang"
+ "golang.org/x/tools/gopls/internal/protocol"
+ "golang.org/x/tools/internal/event"
+)
+
+func (s *server) PrepareTypeHierarchy(ctx context.Context, params *protocol.TypeHierarchyPrepareParams) ([]protocol.TypeHierarchyItem, error) {
+ ctx, done := event.Start(ctx, "server.PrepareTypeHierarchy")
+ defer done()
+
+ fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI)
+ if err != nil {
+ return nil, err
+ }
+ defer release()
+ switch snapshot.FileKind(fh) {
+ case file.Go:
+ return golang.PrepareTypeHierarchy(ctx, snapshot, fh, params.Position)
+ }
+ return nil, fmt.Errorf("unsupported file type: %v", fh)
+}
+
+func (s *server) Subtypes(ctx context.Context, params *protocol.TypeHierarchySubtypesParams) ([]protocol.TypeHierarchyItem, error) {
+ ctx, done := event.Start(ctx, "server.Subtypes")
+ defer done()
+
+ fh, snapshot, release, err := s.fileOf(ctx, params.Item.URI)
+ if err != nil {
+ return nil, err
+ }
+ defer release()
+ switch snapshot.FileKind(fh) {
+ case file.Go:
+ return golang.Subtypes(ctx, snapshot, fh, params.Item)
+ }
+ return nil, fmt.Errorf("unsupported file type: %v", fh)
+}
+
+func (s *server) Supertypes(ctx context.Context, params *protocol.TypeHierarchySupertypesParams) ([]protocol.TypeHierarchyItem, error) {
+ ctx, done := event.Start(ctx, "server.Supertypes")
+ defer done()
+
+ fh, snapshot, release, err := s.fileOf(ctx, params.Item.URI)
+ if err != nil {
+ return nil, err
+ }
+ defer release()
+ switch snapshot.FileKind(fh) {
+ case file.Go:
+ return golang.Supertypes(ctx, snapshot, fh, params.Item)
+ }
+ return nil, fmt.Errorf("unsupported file type: %v", fh)
+}
diff --git a/gopls/internal/server/unimplemented.go b/gopls/internal/server/unimplemented.go
index d3bb07cb647..bd12b25f610 100644
--- a/gopls/internal/server/unimplemented.go
+++ b/gopls/internal/server/unimplemented.go
@@ -74,10 +74,6 @@ func (s *server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeForma
return nil, notImplemented("OnTypeFormatting")
}
-func (s *server) PrepareTypeHierarchy(context.Context, *protocol.TypeHierarchyPrepareParams) ([]protocol.TypeHierarchyItem, error) {
- return nil, notImplemented("PrepareTypeHierarchy")
-}
-
func (s *server) Progress(context.Context, *protocol.ProgressParams) error {
return notImplemented("Progress")
}
@@ -118,14 +114,6 @@ func (s *server) SetTrace(context.Context, *protocol.SetTraceParams) error {
return notImplemented("SetTrace")
}
-func (s *server) Subtypes(context.Context, *protocol.TypeHierarchySubtypesParams) ([]protocol.TypeHierarchyItem, error) {
- return nil, notImplemented("Subtypes")
-}
-
-func (s *server) Supertypes(context.Context, *protocol.TypeHierarchySupertypesParams) ([]protocol.TypeHierarchyItem, error) {
- return nil, notImplemented("Supertypes")
-}
-
func (s *server) WillCreateFiles(context.Context, *protocol.CreateFilesParams) (*protocol.WorkspaceEdit, error) {
return nil, notImplemented("WillCreateFiles")
}
diff --git a/gopls/internal/settings/analysis.go b/gopls/internal/settings/analysis.go
index e914407fe6b..99b55cc6b24 100644
--- a/gopls/internal/settings/analysis.go
+++ b/gopls/internal/settings/analysis.go
@@ -5,6 +5,8 @@
package settings
import (
+ "slices"
+
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/appends"
"golang.org/x/tools/go/analysis/passes/asmdecl"
@@ -21,6 +23,7 @@ import (
"golang.org/x/tools/go/analysis/passes/directive"
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/passes/framepointer"
+ "golang.org/x/tools/go/analysis/passes/hostport"
"golang.org/x/tools/go/analysis/passes/httpresponse"
"golang.org/x/tools/go/analysis/passes/ifaceassert"
"golang.org/x/tools/go/analysis/passes/loopclosure"
@@ -49,7 +52,6 @@ import (
"golang.org/x/tools/gopls/internal/analysis/deprecated"
"golang.org/x/tools/gopls/internal/analysis/embeddirective"
"golang.org/x/tools/gopls/internal/analysis/fillreturns"
- "golang.org/x/tools/gopls/internal/analysis/hostport"
"golang.org/x/tools/gopls/internal/analysis/infertypeargs"
"golang.org/x/tools/gopls/internal/analysis/modernize"
"golang.org/x/tools/gopls/internal/analysis/nonewvars"
@@ -63,14 +65,18 @@ import (
"golang.org/x/tools/gopls/internal/analysis/yield"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/internal/gofix"
+ "honnef.co/go/tools/analysis/lint"
)
-// Analyzer augments a [analysis.Analyzer] with additional LSP configuration.
+var AllAnalyzers = slices.Concat(DefaultAnalyzers, StaticcheckAnalyzers)
+
+// Analyzer augments an [analysis.Analyzer] with additional LSP configuration.
//
// Analyzers are immutable, since they are shared across multiple LSP sessions.
type Analyzer struct {
analyzer *analysis.Analyzer
- nonDefault bool
+ staticcheck *lint.RawDocumentation // only for staticcheck analyzers
+ nonDefault bool // (sense is negated so we can mostly omit it)
actionKinds []protocol.CodeActionKind
severity protocol.DiagnosticSeverity
tags []protocol.DiagnosticTag
@@ -79,9 +85,28 @@ type Analyzer struct {
// Analyzer returns the [analysis.Analyzer] that this Analyzer wraps.
func (a *Analyzer) Analyzer() *analysis.Analyzer { return a.analyzer }
-// EnabledByDefault reports whether the analyzer is enabled by default for all sessions.
+// Enabled reports whether the analyzer is enabled by the options.
// This value can be configured per-analysis in user settings.
-func (a *Analyzer) EnabledByDefault() bool { return !a.nonDefault }
+func (a *Analyzer) Enabled(o *Options) bool {
+ // An explicit setting by name takes precedence.
+ if v, found := o.Analyses[a.Analyzer().Name]; found {
+ return v
+ }
+ if a.staticcheck != nil {
+ // An explicit staticcheck={true,false} setting
+ // enables/disables all staticcheck analyzers.
+ if o.StaticcheckProvided {
+ return o.Staticcheck
+ }
+ // Respect staticcheck's off-by-default options too.
+ // (This applies to only a handful of analyzers.)
+ if a.staticcheck.NonDefault {
+ return false
+ }
+ }
+ // Respect gopls' default setting.
+ return !a.nonDefault
+}
// ActionKinds is the set of kinds of code action this analyzer produces.
//
@@ -126,108 +151,105 @@ func (a *Analyzer) Tags() []protocol.DiagnosticTag { return a.tags }
// String returns the name of this analyzer.
func (a *Analyzer) String() string { return a.analyzer.String() }
-// DefaultAnalyzers holds the set of Analyzers available to all gopls sessions,
-// independent of build version, keyed by analyzer name.
-//
-// It is the source from which gopls/doc/analyzers.md is generated.
-var DefaultAnalyzers = make(map[string]*Analyzer) // initialized below
-
-func init() {
+// DefaultAnalyzers holds the list of Analyzers available to all gopls
+// sessions, independent of build version. It is the source from which
+// gopls/doc/analyzers.md is generated.
+var DefaultAnalyzers = []*Analyzer{
// See [Analyzer.Severity] for guidance on setting analyzer severity below.
- analyzers := []*Analyzer{
- // The traditional vet suite:
- {analyzer: appends.Analyzer},
- {analyzer: asmdecl.Analyzer},
- {analyzer: assign.Analyzer},
- {analyzer: atomic.Analyzer},
- {analyzer: bools.Analyzer},
- {analyzer: buildtag.Analyzer},
- {analyzer: cgocall.Analyzer},
- {analyzer: composite.Analyzer},
- {analyzer: copylock.Analyzer},
- {analyzer: defers.Analyzer},
- {analyzer: deprecated.Analyzer, severity: protocol.SeverityHint, tags: []protocol.DiagnosticTag{protocol.Deprecated}},
- {analyzer: directive.Analyzer},
- {analyzer: errorsas.Analyzer},
- {analyzer: framepointer.Analyzer},
- {analyzer: httpresponse.Analyzer},
- {analyzer: ifaceassert.Analyzer},
- {analyzer: loopclosure.Analyzer},
- {analyzer: lostcancel.Analyzer},
- {analyzer: nilfunc.Analyzer},
- {analyzer: printf.Analyzer},
- {analyzer: shift.Analyzer},
- {analyzer: sigchanyzer.Analyzer},
- {analyzer: slog.Analyzer},
- {analyzer: stdmethods.Analyzer},
- {analyzer: stdversion.Analyzer},
- {analyzer: stringintconv.Analyzer},
- {analyzer: structtag.Analyzer},
- {analyzer: testinggoroutine.Analyzer},
- {analyzer: tests.Analyzer},
- {analyzer: timeformat.Analyzer},
- {analyzer: unmarshal.Analyzer},
- {analyzer: unreachable.Analyzer},
- {analyzer: unsafeptr.Analyzer},
- {analyzer: unusedresult.Analyzer},
-
- // not suitable for vet:
- // - some (nilness, yield) use go/ssa; see #59714.
- // - others don't meet the "frequency" criterion;
- // see GOROOT/src/cmd/vet/README.
- {analyzer: atomicalign.Analyzer},
- {analyzer: deepequalerrors.Analyzer},
- {analyzer: nilness.Analyzer}, // uses go/ssa
- {analyzer: yield.Analyzer}, // uses go/ssa
- {analyzer: sortslice.Analyzer},
- {analyzer: embeddirective.Analyzer},
- {analyzer: waitgroup.Analyzer}, // to appear in cmd/vet@go1.25
- {analyzer: hostport.Analyzer}, // to appear in cmd/vet@go1.25
-
- // disabled due to high false positives
- {analyzer: shadow.Analyzer, nonDefault: true}, // very noisy
- // fieldalignment is not even off-by-default; see #67762.
-
- // simplifiers and modernizers
- //
- // These analyzers offer mere style fixes on correct code,
- // thus they will never appear in cmd/vet and
- // their severity level is "information".
- //
- // gofmt -s suite
- {
- analyzer: simplifycompositelit.Analyzer,
- actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- severity: protocol.SeverityInformation,
- },
- {
- analyzer: simplifyrange.Analyzer,
- actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- severity: protocol.SeverityInformation,
- },
- {
- analyzer: simplifyslice.Analyzer,
- actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- severity: protocol.SeverityInformation,
- },
- // other simplifiers
- {analyzer: gofix.Analyzer, severity: protocol.SeverityHint},
- {analyzer: infertypeargs.Analyzer, severity: protocol.SeverityInformation},
- {analyzer: unusedparams.Analyzer, severity: protocol.SeverityInformation},
- {analyzer: unusedfunc.Analyzer, severity: protocol.SeverityInformation},
- {analyzer: unusedwrite.Analyzer, severity: protocol.SeverityInformation}, // uses go/ssa
- {analyzer: modernize.Analyzer, severity: protocol.SeverityHint},
-
- // type-error analyzers
- // These analyzers enrich go/types errors with suggested fixes.
- // Since they exist only to attach their fixes to type errors, their
- // severity is irrelevant.
- {analyzer: fillreturns.Analyzer},
- {analyzer: nonewvars.Analyzer},
- {analyzer: noresultvalues.Analyzer},
- {analyzer: unusedvariable.Analyzer},
- }
- for _, analyzer := range analyzers {
- DefaultAnalyzers[analyzer.analyzer.Name] = analyzer
- }
+
+ // The traditional vet suite:
+ {analyzer: appends.Analyzer},
+ {analyzer: asmdecl.Analyzer},
+ {analyzer: assign.Analyzer},
+ {analyzer: atomic.Analyzer},
+ {analyzer: bools.Analyzer},
+ {analyzer: buildtag.Analyzer},
+ {analyzer: cgocall.Analyzer},
+ {analyzer: composite.Analyzer},
+ {analyzer: copylock.Analyzer},
+ {analyzer: defers.Analyzer},
+ {
+ analyzer: deprecated.Analyzer,
+ severity: protocol.SeverityHint,
+ tags: []protocol.DiagnosticTag{protocol.Deprecated},
+ },
+ {analyzer: directive.Analyzer},
+ {analyzer: errorsas.Analyzer},
+ {analyzer: framepointer.Analyzer},
+ {analyzer: httpresponse.Analyzer},
+ {analyzer: ifaceassert.Analyzer},
+ {analyzer: loopclosure.Analyzer},
+ {analyzer: lostcancel.Analyzer},
+ {analyzer: nilfunc.Analyzer},
+ {analyzer: printf.Analyzer},
+ {analyzer: shift.Analyzer},
+ {analyzer: sigchanyzer.Analyzer},
+ {analyzer: slog.Analyzer},
+ {analyzer: stdmethods.Analyzer},
+ {analyzer: stdversion.Analyzer},
+ {analyzer: stringintconv.Analyzer},
+ {analyzer: structtag.Analyzer},
+ {analyzer: testinggoroutine.Analyzer},
+ {analyzer: tests.Analyzer},
+ {analyzer: timeformat.Analyzer},
+ {analyzer: unmarshal.Analyzer},
+ {analyzer: unreachable.Analyzer},
+ {analyzer: unsafeptr.Analyzer},
+ {analyzer: unusedresult.Analyzer},
+
+ // not suitable for vet:
+ // - some (nilness, yield) use go/ssa; see #59714.
+ // - others don't meet the "frequency" criterion;
+ // see GOROOT/src/cmd/vet/README.
+ {analyzer: atomicalign.Analyzer},
+ {analyzer: deepequalerrors.Analyzer},
+ {analyzer: nilness.Analyzer}, // uses go/ssa
+ {analyzer: yield.Analyzer}, // uses go/ssa
+ {analyzer: sortslice.Analyzer},
+ {analyzer: embeddirective.Analyzer},
+ {analyzer: waitgroup.Analyzer}, // to appear in cmd/vet@go1.25
+ {analyzer: hostport.Analyzer}, // to appear in cmd/vet@go1.25
+
+ // disabled due to high false positives
+ {analyzer: shadow.Analyzer, nonDefault: true}, // very noisy
+ // fieldalignment is not even off-by-default; see #67762.
+
+ // simplifiers and modernizers
+ //
+ // These analyzers offer mere style fixes on correct code,
+ // thus they will never appear in cmd/vet and
+ // their severity level is "information".
+ //
+ // gofmt -s suite
+ {
+ analyzer: simplifycompositelit.Analyzer,
+ actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
+ severity: protocol.SeverityInformation,
+ },
+ {
+ analyzer: simplifyrange.Analyzer,
+ actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
+ severity: protocol.SeverityInformation,
+ },
+ {
+ analyzer: simplifyslice.Analyzer,
+ actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
+ severity: protocol.SeverityInformation,
+ },
+ // other simplifiers
+ {analyzer: gofix.Analyzer, severity: protocol.SeverityHint},
+ {analyzer: infertypeargs.Analyzer, severity: protocol.SeverityInformation},
+ {analyzer: unusedparams.Analyzer, severity: protocol.SeverityInformation},
+ {analyzer: unusedfunc.Analyzer, severity: protocol.SeverityInformation},
+ {analyzer: unusedwrite.Analyzer, severity: protocol.SeverityInformation}, // uses go/ssa
+ {analyzer: modernize.Analyzer, severity: protocol.SeverityHint},
+
+ // type-error analyzers
+ // These analyzers enrich go/types errors with suggested fixes.
+ // Since they exist only to attach their fixes to type errors, their
+ // severity is irrelevant.
+ {analyzer: fillreturns.Analyzer},
+ {analyzer: nonewvars.Analyzer},
+ {analyzer: noresultvalues.Analyzer},
+ {analyzer: unusedvariable.Analyzer},
}
diff --git a/gopls/internal/settings/codeactionkind.go b/gopls/internal/settings/codeactionkind.go
index f6f8a4df2a4..ebe9606adab 100644
--- a/gopls/internal/settings/codeactionkind.go
+++ b/gopls/internal/settings/codeactionkind.go
@@ -97,6 +97,8 @@ const (
RefactorRewriteMoveParamRight protocol.CodeActionKind = "refactor.rewrite.moveParamRight"
RefactorRewriteSplitLines protocol.CodeActionKind = "refactor.rewrite.splitLines"
RefactorRewriteEliminateDotImport protocol.CodeActionKind = "refactor.rewrite.eliminateDotImport"
+ RefactorRewriteAddTags protocol.CodeActionKind = "refactor.rewrite.addTags"
+ RefactorRewriteRemoveTags protocol.CodeActionKind = "refactor.rewrite.removeTags"
// refactor.inline
RefactorInlineCall protocol.CodeActionKind = "refactor.inline.call"
diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go
index a47a69b0296..8a694854edd 100644
--- a/gopls/internal/settings/settings.go
+++ b/gopls/internal/settings/settings.go
@@ -433,7 +433,8 @@ type FormattingOptions struct {
Gofumpt bool
}
-// Note: DiagnosticOptions must be comparable with reflect.DeepEqual.
+// Note: DiagnosticOptions must be comparable with reflect.DeepEqual,
+// and frob-encodable (no interfaces).
type DiagnosticOptions struct {
// Analyses specify analyses that the user would like to enable or disable.
// A map of the names of analysis passes that should be enabled/disabled.
@@ -452,10 +453,21 @@ type DiagnosticOptions struct {
// ```
Analyses map[string]bool
- // Staticcheck enables additional analyses from staticcheck.io.
+ // Staticcheck configures the default set of analyses staticcheck.io.
// These analyses are documented on
// [Staticcheck's website](https://staticcheck.io/docs/checks/).
- Staticcheck bool `status:"experimental"`
+ //
+ // The "staticcheck" option has three values:
+ // - false: disable all staticcheck analyzers
+ // - true: enable all staticcheck analyzers
+ // - unset: enable a subset of staticcheck analyzers
+ // selected by gopls maintainers for runtime efficiency
+ // and analytic precision.
+ //
+ // Regardless of this setting, individual analyzers can be
+ // selectively enabled or disabled using the `analyses` setting.
+ Staticcheck bool `status:"experimental"`
+ StaticcheckProvided bool `status:"experimental"` // = "staticcheck" was explicitly provided
// Annotations specifies the various kinds of compiler
// optimization details that should be reported as diagnostics
@@ -1187,6 +1199,7 @@ func (o *Options) setOne(name string, value any) (applied []CounterPath, _ error
return counts, nil
case "staticcheck":
+ o.StaticcheckProvided = true
return setBool(&o.Staticcheck, value)
case "local":
diff --git a/gopls/internal/settings/staticcheck.go b/gopls/internal/settings/staticcheck.go
index 6e06e0b44ea..68e48819cfc 100644
--- a/gopls/internal/settings/staticcheck.go
+++ b/gopls/internal/settings/staticcheck.go
@@ -5,19 +5,183 @@
package settings
import (
+ "fmt"
+ "log"
+
+ "golang.org/x/tools/go/analysis"
"golang.org/x/tools/gopls/internal/protocol"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/quickfix"
+ "honnef.co/go/tools/quickfix/qf1001"
+ "honnef.co/go/tools/quickfix/qf1002"
+ "honnef.co/go/tools/quickfix/qf1003"
+ "honnef.co/go/tools/quickfix/qf1004"
+ "honnef.co/go/tools/quickfix/qf1005"
+ "honnef.co/go/tools/quickfix/qf1006"
+ "honnef.co/go/tools/quickfix/qf1007"
+ "honnef.co/go/tools/quickfix/qf1008"
+ "honnef.co/go/tools/quickfix/qf1009"
+ "honnef.co/go/tools/quickfix/qf1010"
+ "honnef.co/go/tools/quickfix/qf1011"
+ "honnef.co/go/tools/quickfix/qf1012"
"honnef.co/go/tools/simple"
+ "honnef.co/go/tools/simple/s1000"
+ "honnef.co/go/tools/simple/s1001"
+ "honnef.co/go/tools/simple/s1002"
+ "honnef.co/go/tools/simple/s1003"
+ "honnef.co/go/tools/simple/s1004"
+ "honnef.co/go/tools/simple/s1005"
+ "honnef.co/go/tools/simple/s1006"
+ "honnef.co/go/tools/simple/s1007"
+ "honnef.co/go/tools/simple/s1008"
+ "honnef.co/go/tools/simple/s1009"
+ "honnef.co/go/tools/simple/s1010"
+ "honnef.co/go/tools/simple/s1011"
+ "honnef.co/go/tools/simple/s1012"
+ "honnef.co/go/tools/simple/s1016"
+ "honnef.co/go/tools/simple/s1017"
+ "honnef.co/go/tools/simple/s1018"
+ "honnef.co/go/tools/simple/s1019"
+ "honnef.co/go/tools/simple/s1020"
+ "honnef.co/go/tools/simple/s1021"
+ "honnef.co/go/tools/simple/s1023"
+ "honnef.co/go/tools/simple/s1024"
+ "honnef.co/go/tools/simple/s1025"
+ "honnef.co/go/tools/simple/s1028"
+ "honnef.co/go/tools/simple/s1029"
+ "honnef.co/go/tools/simple/s1030"
+ "honnef.co/go/tools/simple/s1031"
+ "honnef.co/go/tools/simple/s1032"
+ "honnef.co/go/tools/simple/s1033"
+ "honnef.co/go/tools/simple/s1034"
+ "honnef.co/go/tools/simple/s1035"
+ "honnef.co/go/tools/simple/s1036"
+ "honnef.co/go/tools/simple/s1037"
+ "honnef.co/go/tools/simple/s1038"
+ "honnef.co/go/tools/simple/s1039"
+ "honnef.co/go/tools/simple/s1040"
"honnef.co/go/tools/staticcheck"
+ "honnef.co/go/tools/staticcheck/sa1000"
+ "honnef.co/go/tools/staticcheck/sa1001"
+ "honnef.co/go/tools/staticcheck/sa1002"
+ "honnef.co/go/tools/staticcheck/sa1003"
+ "honnef.co/go/tools/staticcheck/sa1004"
+ "honnef.co/go/tools/staticcheck/sa1005"
+ "honnef.co/go/tools/staticcheck/sa1006"
+ "honnef.co/go/tools/staticcheck/sa1007"
+ "honnef.co/go/tools/staticcheck/sa1008"
+ "honnef.co/go/tools/staticcheck/sa1010"
+ "honnef.co/go/tools/staticcheck/sa1011"
+ "honnef.co/go/tools/staticcheck/sa1012"
+ "honnef.co/go/tools/staticcheck/sa1013"
+ "honnef.co/go/tools/staticcheck/sa1014"
+ "honnef.co/go/tools/staticcheck/sa1015"
+ "honnef.co/go/tools/staticcheck/sa1016"
+ "honnef.co/go/tools/staticcheck/sa1017"
+ "honnef.co/go/tools/staticcheck/sa1018"
+ "honnef.co/go/tools/staticcheck/sa1019"
+ "honnef.co/go/tools/staticcheck/sa1020"
+ "honnef.co/go/tools/staticcheck/sa1021"
+ "honnef.co/go/tools/staticcheck/sa1023"
+ "honnef.co/go/tools/staticcheck/sa1024"
+ "honnef.co/go/tools/staticcheck/sa1025"
+ "honnef.co/go/tools/staticcheck/sa1026"
+ "honnef.co/go/tools/staticcheck/sa1027"
+ "honnef.co/go/tools/staticcheck/sa1028"
+ "honnef.co/go/tools/staticcheck/sa1029"
+ "honnef.co/go/tools/staticcheck/sa1030"
+ "honnef.co/go/tools/staticcheck/sa1031"
+ "honnef.co/go/tools/staticcheck/sa1032"
+ "honnef.co/go/tools/staticcheck/sa2000"
+ "honnef.co/go/tools/staticcheck/sa2001"
+ "honnef.co/go/tools/staticcheck/sa2002"
+ "honnef.co/go/tools/staticcheck/sa2003"
+ "honnef.co/go/tools/staticcheck/sa3000"
+ "honnef.co/go/tools/staticcheck/sa3001"
+ "honnef.co/go/tools/staticcheck/sa4000"
+ "honnef.co/go/tools/staticcheck/sa4001"
+ "honnef.co/go/tools/staticcheck/sa4003"
+ "honnef.co/go/tools/staticcheck/sa4004"
+ "honnef.co/go/tools/staticcheck/sa4005"
+ "honnef.co/go/tools/staticcheck/sa4006"
+ "honnef.co/go/tools/staticcheck/sa4008"
+ "honnef.co/go/tools/staticcheck/sa4009"
+ "honnef.co/go/tools/staticcheck/sa4010"
+ "honnef.co/go/tools/staticcheck/sa4011"
+ "honnef.co/go/tools/staticcheck/sa4012"
+ "honnef.co/go/tools/staticcheck/sa4013"
+ "honnef.co/go/tools/staticcheck/sa4014"
+ "honnef.co/go/tools/staticcheck/sa4015"
+ "honnef.co/go/tools/staticcheck/sa4016"
+ "honnef.co/go/tools/staticcheck/sa4017"
+ "honnef.co/go/tools/staticcheck/sa4018"
+ "honnef.co/go/tools/staticcheck/sa4019"
+ "honnef.co/go/tools/staticcheck/sa4020"
+ "honnef.co/go/tools/staticcheck/sa4021"
+ "honnef.co/go/tools/staticcheck/sa4022"
+ "honnef.co/go/tools/staticcheck/sa4023"
+ "honnef.co/go/tools/staticcheck/sa4024"
+ "honnef.co/go/tools/staticcheck/sa4025"
+ "honnef.co/go/tools/staticcheck/sa4026"
+ "honnef.co/go/tools/staticcheck/sa4027"
+ "honnef.co/go/tools/staticcheck/sa4028"
+ "honnef.co/go/tools/staticcheck/sa4029"
+ "honnef.co/go/tools/staticcheck/sa4030"
+ "honnef.co/go/tools/staticcheck/sa4031"
+ "honnef.co/go/tools/staticcheck/sa4032"
+ "honnef.co/go/tools/staticcheck/sa5000"
+ "honnef.co/go/tools/staticcheck/sa5001"
+ "honnef.co/go/tools/staticcheck/sa5002"
+ "honnef.co/go/tools/staticcheck/sa5003"
+ "honnef.co/go/tools/staticcheck/sa5004"
+ "honnef.co/go/tools/staticcheck/sa5005"
+ "honnef.co/go/tools/staticcheck/sa5007"
+ "honnef.co/go/tools/staticcheck/sa5008"
+ "honnef.co/go/tools/staticcheck/sa5009"
+ "honnef.co/go/tools/staticcheck/sa5010"
+ "honnef.co/go/tools/staticcheck/sa5011"
+ "honnef.co/go/tools/staticcheck/sa5012"
+ "honnef.co/go/tools/staticcheck/sa6000"
+ "honnef.co/go/tools/staticcheck/sa6001"
+ "honnef.co/go/tools/staticcheck/sa6002"
+ "honnef.co/go/tools/staticcheck/sa6003"
+ "honnef.co/go/tools/staticcheck/sa6005"
+ "honnef.co/go/tools/staticcheck/sa6006"
+ "honnef.co/go/tools/staticcheck/sa9001"
+ "honnef.co/go/tools/staticcheck/sa9002"
+ "honnef.co/go/tools/staticcheck/sa9003"
+ "honnef.co/go/tools/staticcheck/sa9004"
+ "honnef.co/go/tools/staticcheck/sa9005"
+ "honnef.co/go/tools/staticcheck/sa9006"
+ "honnef.co/go/tools/staticcheck/sa9007"
+ "honnef.co/go/tools/staticcheck/sa9008"
+ "honnef.co/go/tools/staticcheck/sa9009"
"honnef.co/go/tools/stylecheck"
+ "honnef.co/go/tools/stylecheck/st1000"
+ "honnef.co/go/tools/stylecheck/st1001"
+ "honnef.co/go/tools/stylecheck/st1003"
+ "honnef.co/go/tools/stylecheck/st1005"
+ "honnef.co/go/tools/stylecheck/st1006"
+ "honnef.co/go/tools/stylecheck/st1008"
+ "honnef.co/go/tools/stylecheck/st1011"
+ "honnef.co/go/tools/stylecheck/st1012"
+ "honnef.co/go/tools/stylecheck/st1013"
+ "honnef.co/go/tools/stylecheck/st1015"
+ "honnef.co/go/tools/stylecheck/st1016"
+ "honnef.co/go/tools/stylecheck/st1017"
+ "honnef.co/go/tools/stylecheck/st1018"
+ "honnef.co/go/tools/stylecheck/st1019"
+ "honnef.co/go/tools/stylecheck/st1020"
+ "honnef.co/go/tools/stylecheck/st1021"
+ "honnef.co/go/tools/stylecheck/st1022"
+ "honnef.co/go/tools/stylecheck/st1023"
)
-// StaticcheckAnalzyers describes available Staticcheck analyzers, keyed by
-// analyzer name.
-var StaticcheckAnalyzers = make(map[string]*Analyzer) // written by analysis_.go
+// StaticcheckAnalyzers lists available Staticcheck analyzers.
+var StaticcheckAnalyzers = initStaticcheckAnalyzers()
+
+func initStaticcheckAnalyzers() (res []*Analyzer) {
-func init() {
mapSeverity := func(severity lint.Severity) protocol.DiagnosticSeverity {
switch severity {
case lint.SeverityError:
@@ -36,28 +200,251 @@ func init() {
return protocol.SeverityWarning
}
}
- add := func(analyzers []*lint.Analyzer, skip map[string]struct{}) {
- for _, a := range analyzers {
- if _, ok := skip[a.Analyzer.Name]; ok {
- continue
+
+ // We can't import buildir.Analyzer directly, so grab it from another analyzer.
+ buildir := sa1000.SCAnalyzer.Analyzer.Requires[0]
+ if buildir.Name != "buildir" {
+ panic("sa1000.Requires[0] is not buildir")
+ }
+
+ add := func(a *lint.Analyzer, dflt bool) {
+ // Assert that no analyzer that requires "buildir",
+ // even indirectly, is enabled by default.
+ if dflt {
+ var visit func(aa *analysis.Analyzer)
+ visit = func(aa *analysis.Analyzer) {
+ if aa == buildir {
+ log.Fatalf("%s requires buildir (perhaps indirectly) yet is enabled by default", a.Analyzer.Name)
+ }
+ for _, req := range aa.Requires {
+ visit(req)
+ }
}
+ visit(a.Analyzer)
+ }
+ res = append(res, &Analyzer{
+ analyzer: a.Analyzer,
+ staticcheck: a.Doc,
+ nonDefault: !dflt,
+ severity: mapSeverity(a.Doc.Severity),
+ })
+ }
- StaticcheckAnalyzers[a.Analyzer.Name] = &Analyzer{
- analyzer: a.Analyzer,
- nonDefault: a.Doc.NonDefault,
- severity: mapSeverity(a.Doc.Severity),
+ type M = map[*lint.Analyzer]any // value = true|false|nil
+
+ addAll := func(suite string, upstream []*lint.Analyzer, config M) {
+ for _, a := range upstream {
+ v, ok := config[a]
+ if !ok {
+ panic(fmt.Sprintf("%s.Analyzers includes %s but config mapping does not; settings audit required", suite, a.Analyzer.Name))
+ }
+ if v != nil {
+ add(a, v.(bool))
}
}
}
- add(simple.Analyzers, nil)
- add(staticcheck.Analyzers, map[string]struct{}{
- // This check conflicts with the vet printf check (golang/go#34494).
- "SA5009": {},
- // This check relies on facts from dependencies, which
- // we don't currently compute.
- "SA5011": {},
+ // For each analyzer in the four suites provided by
+ // staticcheck, we provide a complete configuration, mapping
+ // it to a boolean, indicating whether it should be on by
+ // default in gopls, or nil to indicate explicitly that it has
+ // been excluded (e.g. because it is redundant with an
+ // existing vet analyzer such as printf, waitgroup, appends).
+ //
+ // This approach ensures that as suites grow, we make an
+ // affirmative decision, positive or negative, about adding
+ // new items.
+ //
+ // An analyzer may be off by default if:
+ // - it requires, even indirectly, "buildir", which is like
+ // buildssa but uses facts, making it expensive;
+ // - it has significant false positives;
+ // - it reports on non-problematic style issues;
+ // - its fixes are lossy (e.g. of comments) or not always sound;
+ // - it reports "maybes", not "definites" (e.g. sa9001).
+ // - it reports on harmless stylistic choices that may have
+ // been chosen deliberately for clarity or emphasis (e.g. s1005).
+ // - it makes deductions from build tags that are not true
+ // for all configurations.
+
+ addAll("simple", simple.Analyzers, M{
+ s1000.SCAnalyzer: true,
+ s1001.SCAnalyzer: true,
+ s1002.SCAnalyzer: false, // makes unsound deductions from build tags
+ s1003.SCAnalyzer: true,
+ s1004.SCAnalyzer: true,
+ s1005.SCAnalyzer: false, // not a correctness/style issue
+ s1006.SCAnalyzer: false, // makes unsound deductions from build tags
+ s1007.SCAnalyzer: true,
+ s1008.SCAnalyzer: false, // may lose important comments
+ s1009.SCAnalyzer: true,
+ s1010.SCAnalyzer: true,
+ s1011.SCAnalyzer: false, // requires buildir
+ s1012.SCAnalyzer: true,
+ s1016.SCAnalyzer: false, // may rely on coincidental structural subtyping
+ s1017.SCAnalyzer: true,
+ s1018.SCAnalyzer: true,
+ s1019.SCAnalyzer: true,
+ s1020.SCAnalyzer: true,
+ s1021.SCAnalyzer: false, // may lose important comments
+ s1023.SCAnalyzer: true,
+ s1024.SCAnalyzer: true,
+ s1025.SCAnalyzer: false, // requires buildir
+ s1028.SCAnalyzer: true,
+ s1029.SCAnalyzer: false, // requires buildir
+ s1030.SCAnalyzer: true, // (tentative: see docs,
+ s1031.SCAnalyzer: true,
+ s1032.SCAnalyzer: true,
+ s1033.SCAnalyzer: true,
+ s1034.SCAnalyzer: true,
+ s1035.SCAnalyzer: true,
+ s1036.SCAnalyzer: true,
+ s1037.SCAnalyzer: true,
+ s1038.SCAnalyzer: true,
+ s1039.SCAnalyzer: true,
+ s1040.SCAnalyzer: true,
+ })
+
+ addAll("stylecheck", stylecheck.Analyzers, M{
+ // These are all slightly too opinionated to be on by default.
+ st1000.SCAnalyzer: false,
+ st1001.SCAnalyzer: false,
+ st1003.SCAnalyzer: false,
+ st1005.SCAnalyzer: false,
+ st1006.SCAnalyzer: false,
+ st1008.SCAnalyzer: false,
+ st1011.SCAnalyzer: false,
+ st1012.SCAnalyzer: false,
+ st1013.SCAnalyzer: false,
+ st1015.SCAnalyzer: false,
+ st1016.SCAnalyzer: false,
+ st1017.SCAnalyzer: false,
+ st1018.SCAnalyzer: false,
+ st1019.SCAnalyzer: false,
+ st1020.SCAnalyzer: false,
+ st1021.SCAnalyzer: false,
+ st1022.SCAnalyzer: false,
+ st1023.SCAnalyzer: false,
+ })
+
+ // These are not bug fixes but code transformations: some
+ // reversible and value-neutral, of the kind typically listed
+ // on the VS Code's Refactor/Source Action/Quick Fix menus.
+ //
+ // TODO(adonovan): plumb these to the appropriate menu,
+ // as we do for code actions such as split/join lines.
+ addAll("quickfix", quickfix.Analyzers, M{
+ qf1001.SCAnalyzer: false, // not always a style improvement
+ qf1002.SCAnalyzer: true,
+ qf1003.SCAnalyzer: true,
+ qf1004.SCAnalyzer: true,
+ qf1005.SCAnalyzer: false, // not always a style improvement
+ qf1006.SCAnalyzer: false, // may lose important comments
+ qf1007.SCAnalyzer: false, // may lose important comments
+ qf1008.SCAnalyzer: false, // not always a style improvement
+ qf1009.SCAnalyzer: true,
+ qf1010.SCAnalyzer: true,
+ qf1011.SCAnalyzer: false, // not always a style improvement
+ qf1012.SCAnalyzer: true,
+ })
+
+ addAll("staticcheck", staticcheck.Analyzers, M{
+ sa1000.SCAnalyzer: false, // requires buildir
+ sa1001.SCAnalyzer: true,
+ sa1002.SCAnalyzer: false, // requires buildir
+ sa1003.SCAnalyzer: false, // requires buildir
+ sa1004.SCAnalyzer: true,
+ sa1005.SCAnalyzer: true,
+ sa1006.SCAnalyzer: nil, // redundant wrt 'printf'
+ sa1007.SCAnalyzer: false, // requires buildir
+ sa1008.SCAnalyzer: true,
+ sa1010.SCAnalyzer: false, // requires buildir
+ sa1011.SCAnalyzer: false, // requires buildir
+ sa1012.SCAnalyzer: true,
+ sa1013.SCAnalyzer: true,
+ sa1014.SCAnalyzer: false, // requires buildir
+ sa1015.SCAnalyzer: false, // requires buildir
+ sa1016.SCAnalyzer: true,
+ sa1017.SCAnalyzer: false, // requires buildir
+ sa1018.SCAnalyzer: false, // requires buildir
+ sa1019.SCAnalyzer: nil, // redundant wrt 'deprecated'
+ sa1020.SCAnalyzer: false, // requires buildir
+ sa1021.SCAnalyzer: false, // requires buildir
+ sa1023.SCAnalyzer: false, // requires buildir
+ sa1024.SCAnalyzer: false, // requires buildir
+ sa1025.SCAnalyzer: false, // requires buildir
+ sa1026.SCAnalyzer: false, // requires buildir
+ sa1027.SCAnalyzer: false, // requires buildir
+ sa1028.SCAnalyzer: false, // requires buildir
+ sa1029.SCAnalyzer: false, // requires buildir
+ sa1030.SCAnalyzer: false, // requires buildir
+ sa1031.SCAnalyzer: false, // requires buildir
+ sa1032.SCAnalyzer: false, // requires buildir
+ sa2000.SCAnalyzer: nil, // redundant wrt 'waitgroup'
+ sa2001.SCAnalyzer: true,
+ sa2002.SCAnalyzer: false, // requires buildir
+ sa2003.SCAnalyzer: false, // requires buildir
+ sa3000.SCAnalyzer: true,
+ sa3001.SCAnalyzer: true,
+ sa4000.SCAnalyzer: true,
+ sa4001.SCAnalyzer: true,
+ sa4003.SCAnalyzer: true,
+ sa4004.SCAnalyzer: true,
+ sa4005.SCAnalyzer: false, // requires buildir
+ sa4006.SCAnalyzer: false, // requires buildir
+ sa4008.SCAnalyzer: false, // requires buildir
+ sa4009.SCAnalyzer: false, // requires buildir
+ sa4010.SCAnalyzer: false, // requires buildir
+ sa4011.SCAnalyzer: true,
+ sa4012.SCAnalyzer: false, // requires buildir
+ sa4013.SCAnalyzer: true,
+ sa4014.SCAnalyzer: true,
+ sa4015.SCAnalyzer: false, // requires buildir
+ sa4016.SCAnalyzer: true,
+ sa4017.SCAnalyzer: false, // requires buildir
+ sa4018.SCAnalyzer: false, // requires buildir
+ sa4019.SCAnalyzer: true,
+ sa4020.SCAnalyzer: true,
+ sa4021.SCAnalyzer: nil, // redundant wrt 'appends'
+ sa4022.SCAnalyzer: true,
+ sa4023.SCAnalyzer: false, // requires buildir
+ sa4024.SCAnalyzer: true,
+ sa4025.SCAnalyzer: true,
+ sa4026.SCAnalyzer: true,
+ sa4027.SCAnalyzer: true,
+ sa4028.SCAnalyzer: true,
+ sa4029.SCAnalyzer: true,
+ sa4030.SCAnalyzer: true,
+ sa4031.SCAnalyzer: false, // requires buildir
+ sa4032.SCAnalyzer: true,
+ sa5000.SCAnalyzer: false, // requires buildir
+ sa5001.SCAnalyzer: true,
+ sa5002.SCAnalyzer: false, // makes unsound deductions from build tags
+ sa5003.SCAnalyzer: true,
+ sa5004.SCAnalyzer: true,
+ sa5005.SCAnalyzer: false, // requires buildir
+ sa5007.SCAnalyzer: false, // requires buildir
+ sa5008.SCAnalyzer: true,
+ sa5009.SCAnalyzer: nil, // requires buildir; redundant wrt 'printf' (#34494,
+ sa5010.SCAnalyzer: false, // requires buildir
+ sa5011.SCAnalyzer: false, // requires buildir
+ sa5012.SCAnalyzer: false, // requires buildir
+ sa6000.SCAnalyzer: false, // requires buildir
+ sa6001.SCAnalyzer: false, // requires buildir
+ sa6002.SCAnalyzer: false, // requires buildir
+ sa6003.SCAnalyzer: false, // requires buildir
+ sa6005.SCAnalyzer: true,
+ sa6006.SCAnalyzer: true,
+ sa9001.SCAnalyzer: false, // reports a "maybe" bug (low signal/noise,
+ sa9002.SCAnalyzer: true,
+ sa9003.SCAnalyzer: false, // requires buildir; NonDefault
+ sa9004.SCAnalyzer: true,
+ sa9005.SCAnalyzer: false, // requires buildir
+ sa9006.SCAnalyzer: true,
+ sa9007.SCAnalyzer: false, // requires buildir
+ sa9008.SCAnalyzer: false, // requires buildir
+ sa9009.SCAnalyzer: true,
})
- add(stylecheck.Analyzers, nil)
- add(quickfix.Analyzers, nil)
+
+ return res
}
diff --git a/gopls/internal/settings/vet_test.go b/gopls/internal/settings/vet_test.go
index 56daf678c43..f70b72e2151 100644
--- a/gopls/internal/settings/vet_test.go
+++ b/gopls/internal/settings/vet_test.go
@@ -41,7 +41,7 @@ func TestVetSuite(t *testing.T) {
out := fmt.Sprint(cmd.Stdout)
_, out, _ = strings.Cut(out, "Registered analyzers:\n\n")
out, _, _ = strings.Cut(out, "\n\n")
- for _, line := range strings.Split(out, "\n") {
+ for line := range strings.SplitSeq(out, "\n") {
name := strings.Fields(line)[0]
if !goplsAnalyzers[name] {
t.Errorf("gopls lacks vet analyzer %q", name)
diff --git a/gopls/internal/telemetry/cmd/stacks/stacks.go b/gopls/internal/telemetry/cmd/stacks/stacks.go
index f8caabd67e6..cb0a21b4ec2 100644
--- a/gopls/internal/telemetry/cmd/stacks/stacks.go
+++ b/gopls/internal/telemetry/cmd/stacks/stacks.go
@@ -741,7 +741,7 @@ func newIssue(pcfg ProgramConfig, stack, id, jsonURL string, counts map[Info]int
// lines around the PC in this symbol.
var symbol string
outer:
- for _, line := range strings.Split(stack, "\n") {
+ for line := range strings.SplitSeq(stack, "\n") {
for _, s := range pcfg.IgnoreSymbolContains {
if strings.Contains(line, s) {
continue outer // not interesting
@@ -814,7 +814,7 @@ func writeStackComment(body *bytes.Buffer, stack, id string, jsonURL string, cou
}
// Parse the stack and get the symbol names out.
- for _, frame := range strings.Split(stack, "\n") {
+ for frame := range strings.SplitSeq(stack, "\n") {
if url := frameURL(pclntab, info, frame); url != "" {
fmt.Fprintf(body, "- [`%s`](%s)\n", frame, url)
} else {
diff --git a/gopls/internal/template/highlight.go b/gopls/internal/template/highlight.go
index 39812cfd0ba..c6b0c0f778e 100644
--- a/gopls/internal/template/highlight.go
+++ b/gopls/internal/template/highlight.go
@@ -70,7 +70,7 @@ func markWordInToken(p *Parsed, wordAt string) ([]protocol.DocumentHighlight, er
}
for _, tok := range p.tokens {
got := pat.FindAllIndex(p.buf[tok.Start:tok.End], -1)
- for i := 0; i < len(got); i++ {
+ for i := range got {
ans = append(ans, protocol.DocumentHighlight{
Range: p.Range(got[i][0], got[i][1]-got[i][0]),
Kind: protocol.Text,
diff --git a/gopls/internal/test/integration/bench/codeaction_test.go b/gopls/internal/test/integration/bench/codeaction_test.go
index 679f2d4cf3d..4bba9e6f317 100644
--- a/gopls/internal/test/integration/bench/codeaction_test.go
+++ b/gopls/internal/test/integration/bench/codeaction_test.go
@@ -28,7 +28,7 @@ func BenchmarkCodeAction(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
env.CodeActionForFile(test.file, nil)
}
})
@@ -52,7 +52,7 @@ func BenchmarkCodeActionFollowingEdit(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
edits := atomic.AddInt64(&editID, 1)
env.EditBuffer(test.file, protocol.TextEdit{
Range: protocol.Range{
diff --git a/gopls/internal/test/integration/bench/completion_test.go b/gopls/internal/test/integration/bench/completion_test.go
index 48ecf0cefd6..2140e30d123 100644
--- a/gopls/internal/test/integration/bench/completion_test.go
+++ b/gopls/internal/test/integration/bench/completion_test.go
@@ -53,7 +53,7 @@ func benchmarkCompletion(options completionBenchOptions, b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
if options.beforeCompletion != nil {
options.beforeCompletion(env)
}
@@ -314,13 +314,11 @@ func runCompletion(b *testing.B, test completionTest, followingEdit, completeUni
}
}
- b.ResetTimer()
-
if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "completion")); stopAndRecord != nil {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
if followingEdit {
editPlaceholder()
}
diff --git a/gopls/internal/test/integration/bench/definition_test.go b/gopls/internal/test/integration/bench/definition_test.go
index b703378a27b..e456d5a7c87 100644
--- a/gopls/internal/test/integration/bench/definition_test.go
+++ b/gopls/internal/test/integration/bench/definition_test.go
@@ -38,7 +38,7 @@ func BenchmarkDefinition(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
env.GoToDefinition(loc) // pre-warm the query
}
})
diff --git a/gopls/internal/test/integration/bench/diagnostic_test.go b/gopls/internal/test/integration/bench/diagnostic_test.go
index ce8a84d9eb2..6dd00afd5d8 100644
--- a/gopls/internal/test/integration/bench/diagnostic_test.go
+++ b/gopls/internal/test/integration/bench/diagnostic_test.go
@@ -58,9 +58,7 @@ func BenchmarkDiagnosePackageFiles(b *testing.B) {
defer stopAndRecord()
}
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
edit()
var wg sync.WaitGroup
for _, file := range files {
diff --git a/gopls/internal/test/integration/bench/didchange_test.go b/gopls/internal/test/integration/bench/didchange_test.go
index b1613bb1b03..aa87a4f9b0e 100644
--- a/gopls/internal/test/integration/bench/didchange_test.go
+++ b/gopls/internal/test/integration/bench/didchange_test.go
@@ -56,7 +56,7 @@ func BenchmarkDidChange(b *testing.B) {
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
edit()
env.Await(env.StartedChange())
}
@@ -142,7 +142,7 @@ func runChangeDiagnosticsBenchmark(b *testing.B, test changeTest, save bool, ope
if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, operation)); stopAndRecord != nil {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
edits := atomic.AddInt64(&editID, 1)
env.EditBuffer(test.file, protocol.TextEdit{
Range: protocol.Range{
diff --git a/gopls/internal/test/integration/bench/hover_test.go b/gopls/internal/test/integration/bench/hover_test.go
index c3b0c6bc0cb..07a60c354f7 100644
--- a/gopls/internal/test/integration/bench/hover_test.go
+++ b/gopls/internal/test/integration/bench/hover_test.go
@@ -39,7 +39,7 @@ func BenchmarkHover(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
env.Hover(loc) // pre-warm the query
}
})
diff --git a/gopls/internal/test/integration/bench/implementations_test.go b/gopls/internal/test/integration/bench/implementations_test.go
index b7e08aa3141..0c3acca89b1 100644
--- a/gopls/internal/test/integration/bench/implementations_test.go
+++ b/gopls/internal/test/integration/bench/implementations_test.go
@@ -36,7 +36,7 @@ func BenchmarkImplementations(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
env.Implementations(loc)
}
})
diff --git a/gopls/internal/test/integration/bench/imports_test.go b/gopls/internal/test/integration/bench/imports_test.go
index 97419cb10c5..3f47a561681 100644
--- a/gopls/internal/test/integration/bench/imports_test.go
+++ b/gopls/internal/test/integration/bench/imports_test.go
@@ -29,9 +29,7 @@ func BenchmarkInitialGoimportsScan(b *testing.B) {
repo := getRepo(b, "tools") // since this a test of module cache scanning, any repo will do
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
func() {
// Unfortunately we (intentionally) don't support resetting the module
// cache scan state, so in order to have an accurate benchmark we must
diff --git a/gopls/internal/test/integration/bench/iwl_test.go b/gopls/internal/test/integration/bench/iwl_test.go
index 09ccb301a58..0f94b6a3857 100644
--- a/gopls/internal/test/integration/bench/iwl_test.go
+++ b/gopls/internal/test/integration/bench/iwl_test.go
@@ -41,7 +41,7 @@ func BenchmarkInitialWorkspaceLoad(b *testing.B) {
sharedEnv := repo.sharedEnv(b)
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
doIWL(b, sharedEnv.Sandbox.GOPATH(), repo, nil)
}
})
@@ -61,7 +61,7 @@ func BenchmarkInitialWorkspaceLoadOpenFiles(b *testing.B) {
sharedEnv := repo.sharedEnv(b)
b.ResetTimer()
- for range b.N {
+ for b.Loop() {
doIWL(b, sharedEnv.Sandbox.GOPATH(), repo, []string{t.file})
}
})
diff --git a/gopls/internal/test/integration/bench/references_test.go b/gopls/internal/test/integration/bench/references_test.go
index aeaba6f5683..7a4152a8b70 100644
--- a/gopls/internal/test/integration/bench/references_test.go
+++ b/gopls/internal/test/integration/bench/references_test.go
@@ -36,7 +36,7 @@ func BenchmarkReferences(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
env.References(loc)
}
})
diff --git a/gopls/internal/test/integration/bench/reload_test.go b/gopls/internal/test/integration/bench/reload_test.go
index b93b76f945d..1a40cc5eba1 100644
--- a/gopls/internal/test/integration/bench/reload_test.go
+++ b/gopls/internal/test/integration/bench/reload_test.go
@@ -48,7 +48,7 @@ func BenchmarkReload(b *testing.B) {
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
// Mutate the file. This may result in cache hits, but that's OK: the
// goal is to ensure that we don't reload more than just the current
// package.
diff --git a/gopls/internal/test/integration/bench/rename_test.go b/gopls/internal/test/integration/bench/rename_test.go
index ca5ed5f4397..32cbace5faa 100644
--- a/gopls/internal/test/integration/bench/rename_test.go
+++ b/gopls/internal/test/integration/bench/rename_test.go
@@ -39,7 +39,7 @@ func BenchmarkRename(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
names++
newName := fmt.Sprintf("%s%d", test.baseName, names)
env.Rename(loc, newName)
diff --git a/gopls/internal/test/integration/bench/tests_test.go b/gopls/internal/test/integration/bench/tests_test.go
index 3bc69ef95e1..77ba88c7156 100644
--- a/gopls/internal/test/integration/bench/tests_test.go
+++ b/gopls/internal/test/integration/bench/tests_test.go
@@ -75,7 +75,7 @@ func BenchmarkPackagesCommand(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
executePackagesCmd(b, env, args)
}
})
diff --git a/gopls/internal/test/integration/bench/typing_test.go b/gopls/internal/test/integration/bench/typing_test.go
index 78bd16cef5b..b32e707858f 100644
--- a/gopls/internal/test/integration/bench/typing_test.go
+++ b/gopls/internal/test/integration/bench/typing_test.go
@@ -41,7 +41,7 @@ func BenchmarkTyping(b *testing.B) {
defer stopAndRecord()
}
ticker := time.NewTicker(delay)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
edits := atomic.AddInt64(&editID, 1)
env.EditBuffer(test.file, protocol.TextEdit{
Range: protocol.Range{
diff --git a/gopls/internal/test/integration/bench/workspace_symbols_test.go b/gopls/internal/test/integration/bench/workspace_symbols_test.go
index d3e1d207b2d..fb914563191 100644
--- a/gopls/internal/test/integration/bench/workspace_symbols_test.go
+++ b/gopls/internal/test/integration/bench/workspace_symbols_test.go
@@ -35,7 +35,7 @@ func BenchmarkWorkspaceSymbols(b *testing.B) {
defer stopAndRecord()
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
env.Symbol(*symbolQuery)
}
})
diff --git a/gopls/internal/test/integration/diagnostics/invalidation_test.go b/gopls/internal/test/integration/diagnostics/invalidation_test.go
index e8d39c3c38a..0ee23eda003 100644
--- a/gopls/internal/test/integration/diagnostics/invalidation_test.go
+++ b/gopls/internal/test/integration/diagnostics/invalidation_test.go
@@ -82,7 +82,7 @@ func _() {
}
msg := d.Diagnostics[0].Message
- for i := 0; i < 5; i++ {
+ for i := range 5 {
before := d.Version
env.RegexpReplace("main.go", "Irrelevant comment #.", fmt.Sprintf("Irrelevant comment #%d", i))
env.AfterChange(
diff --git a/gopls/internal/test/integration/diagnostics/undeclared_test.go b/gopls/internal/test/integration/diagnostics/undeclared_test.go
index 5579c0752d7..2b399f52f3c 100644
--- a/gopls/internal/test/integration/diagnostics/undeclared_test.go
+++ b/gopls/internal/test/integration/diagnostics/undeclared_test.go
@@ -5,6 +5,7 @@
package diagnostics
import (
+ "slices"
"testing"
"golang.org/x/tools/gopls/internal/protocol"
@@ -34,12 +35,7 @@ func _() int {
`
Run(t, src, func(t *testing.T, env *Env) {
isUnnecessary := func(diag protocol.Diagnostic) bool {
- for _, tag := range diag.Tags {
- if tag == protocol.Unnecessary {
- return true
- }
- }
- return false
+ return slices.Contains(diag.Tags, protocol.Unnecessary)
}
// 'x' is undeclared, but still necessary.
diff --git a/gopls/internal/test/integration/fake/edit_test.go b/gopls/internal/test/integration/fake/edit_test.go
index 0d7ac18c414..f0a44846d31 100644
--- a/gopls/internal/test/integration/fake/edit_test.go
+++ b/gopls/internal/test/integration/fake/edit_test.go
@@ -79,7 +79,6 @@ func TestApplyEdits(t *testing.T) {
}
for _, test := range tests {
- test := test
t.Run(test.label, func(t *testing.T) {
got, err := applyEdits(protocol.NewMapper("", []byte(test.content)), test.edits, false)
if (err != nil) != test.wantErr {
diff --git a/gopls/internal/test/integration/fake/editor.go b/gopls/internal/test/integration/fake/editor.go
index 01f3de8aba9..a2dabf61c46 100644
--- a/gopls/internal/test/integration/fake/editor.go
+++ b/gopls/internal/test/integration/fake/editor.go
@@ -10,6 +10,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "maps"
"math/rand/v2"
"os"
"path"
@@ -108,6 +109,14 @@ type EditorConfig struct {
// To explicitly send no workspace folders, use an empty (non-nil) slice.
WorkspaceFolders []string
+ // NoDefaultWorkspaceFiles is used to specify whether the fake editor
+ // should give a default workspace folder when WorkspaceFolders is nil.
+ // When it's true, the editor will pass original WorkspaceFolders as is to the LSP server.
+ NoDefaultWorkspaceFiles bool
+
+ // RelRootPath is the root path which will be converted to rootUri to configure on the LSP server.
+ RelRootPath string
+
// Whether to edit files with windows line endings.
WindowsLineEndings bool
@@ -253,12 +262,8 @@ func (e *Editor) Client() *Client {
// makeSettings builds the settings map for use in LSP settings RPCs.
func makeSettings(sandbox *Sandbox, config EditorConfig, scopeURI *protocol.URI) map[string]any {
env := make(map[string]string)
- for k, v := range sandbox.GoEnv() {
- env[k] = v
- }
- for k, v := range config.Env {
- env[k] = v
- }
+ maps.Copy(env, sandbox.GoEnv())
+ maps.Copy(env, config.Env)
for k, v := range env {
v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", sandbox.Workdir.RootURI().Path())
env[k] = v
@@ -299,9 +304,7 @@ func makeSettings(sandbox *Sandbox, config EditorConfig, scopeURI *protocol.URI)
}
}
if closestSettings != nil {
- for k, v := range closestSettings {
- settings[k] = v
- }
+ maps.Copy(settings, closestSettings)
}
}
@@ -322,8 +325,9 @@ func (e *Editor) initialize(ctx context.Context) error {
Version: "v1.0.0",
}
params.InitializationOptions = makeSettings(e.sandbox, config, nil)
- params.WorkspaceFolders = makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders)
+ params.WorkspaceFolders = makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders, config.NoDefaultWorkspaceFiles)
+ params.RootURI = protocol.DocumentURI(makeRootURI(e.sandbox, config.RelRootPath))
capabilities, err := clientCapabilities(config)
if err != nil {
return fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err)
@@ -434,12 +438,7 @@ func marshalUnmarshal[T any](v any) (T, error) {
// HasCommand reports whether the connected server supports the command with the given ID.
func (e *Editor) HasCommand(cmd command.Command) bool {
- for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands {
- if command == cmd.String() {
- return true
- }
- }
- return false
+ return slices.Contains(e.serverCapabilities.ExecuteCommandProvider.Commands, cmd.String())
}
// Examples: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
@@ -447,7 +446,10 @@ var uriRE = regexp.MustCompile(`^[a-z][a-z0-9+\-.]*://\S+`)
// makeWorkspaceFolders creates a slice of workspace folders to use for
// this editing session, based on the editor configuration.
-func makeWorkspaceFolders(sandbox *Sandbox, paths []string) (folders []protocol.WorkspaceFolder) {
+func makeWorkspaceFolders(sandbox *Sandbox, paths []string, useEmpty bool) (folders []protocol.WorkspaceFolder) {
+ if len(paths) == 0 && useEmpty {
+ return nil
+ }
if len(paths) == 0 {
paths = []string{string(sandbox.Workdir.RelativeTo)}
}
@@ -466,6 +468,14 @@ func makeWorkspaceFolders(sandbox *Sandbox, paths []string) (folders []protocol.
return folders
}
+func makeRootURI(sandbox *Sandbox, path string) string {
+ uri := path
+ if !uriRE.MatchString(path) { // relative file path
+ uri = string(sandbox.Workdir.URI(path))
+ }
+ return uri
+}
+
// onFileChanges is registered to be called by the Workdir on any writes that
// go through the Workdir API. It is called synchronously by the Workdir.
func (e *Editor) onFileChanges(ctx context.Context, evts []protocol.FileEvent) {
@@ -1159,11 +1169,8 @@ func (e *Editor) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCom
var match bool
if e.serverCapabilities.ExecuteCommandProvider != nil {
// Ensure that this command was actually listed as a supported command.
- for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands {
- if command == params.Command {
- match = true
- break
- }
+ if slices.Contains(e.serverCapabilities.ExecuteCommandProvider.Commands, params.Command) {
+ match = true
}
}
if !match {
@@ -1645,8 +1652,8 @@ func (e *Editor) ChangeWorkspaceFolders(ctx context.Context, folders []string) e
config := e.Config()
// capture existing folders so that we can compute the change.
- oldFolders := makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders)
- newFolders := makeWorkspaceFolders(e.sandbox, folders)
+ oldFolders := makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders, config.NoDefaultWorkspaceFiles)
+ newFolders := makeWorkspaceFolders(e.sandbox, folders, config.NoDefaultWorkspaceFiles)
config.WorkspaceFolders = folders
e.SetConfig(config)
diff --git a/gopls/internal/test/integration/misc/compileropt_test.go b/gopls/internal/test/integration/misc/compileropt_test.go
index 68138fabc43..a02a5dddebd 100644
--- a/gopls/internal/test/integration/misc/compileropt_test.go
+++ b/gopls/internal/test/integration/misc/compileropt_test.go
@@ -5,6 +5,7 @@
package misc
import (
+ "fmt"
"runtime"
"testing"
@@ -44,6 +45,7 @@ func main() {
if err != nil {
t.Fatal(err)
}
+
params := &protocol.ExecuteCommandParams{
Command: docAction.Command.Command,
Arguments: docAction.Command.Arguments,
@@ -229,3 +231,13 @@ func cond[T any](cond bool, x, y T) T {
return y
}
}
+
+// codeActionByKind returns the first action of (exactly) the specified kind, or an error.
+func codeActionByKind(actions []protocol.CodeAction, kind protocol.CodeActionKind) (*protocol.CodeAction, error) {
+ for _, act := range actions {
+ if act.Kind == kind {
+ return &act, nil
+ }
+ }
+ return nil, fmt.Errorf("can't find action with kind %s, only %#v", kind, actions)
+}
diff --git a/gopls/internal/test/integration/misc/failures_test.go b/gopls/internal/test/integration/misc/failures_test.go
index 81fa17deb9b..543e36a9e44 100644
--- a/gopls/internal/test/integration/misc/failures_test.go
+++ b/gopls/internal/test/integration/misc/failures_test.go
@@ -7,8 +7,8 @@ package misc
import (
"testing"
- . "golang.org/x/tools/gopls/internal/test/integration"
"golang.org/x/tools/gopls/internal/test/compare"
+ . "golang.org/x/tools/gopls/internal/test/integration"
)
// This is a slight variant of TestHoverOnError in definition_test.go
diff --git a/gopls/internal/test/integration/misc/link_test.go b/gopls/internal/test/integration/misc/link_test.go
index 53b0f0818f3..079d84cb6ee 100644
--- a/gopls/internal/test/integration/misc/link_test.go
+++ b/gopls/internal/test/integration/misc/link_test.go
@@ -5,9 +5,12 @@
package misc
import (
+ "path/filepath"
+ "slices"
"strings"
"testing"
+ "golang.org/x/tools/gopls/internal/protocol"
. "golang.org/x/tools/gopls/internal/test/integration"
)
@@ -19,15 +22,35 @@ module mod.test
go 1.12
require import.test v1.2.3
+
+require replace.test v1.2.3
+replace replace.test => replace.test v1.2.4
+
+require replace.fixed.test v1.2.3
+replace replace.fixed.test v1.2.3 => replace.fixed.test v1.2.4
+
+require replace.another.test v1.2.3
+replace replace.another.test => another.test v1.2.3
+
+
+replace example.com/non-exist => ./
+replace example.com/non-exist1 => ../work/
+
-- main.go --
package main
import "import.test/pkg"
+import "replace.test/replace"
+import "replace.fixed.test/fixed"
+import "replace.another.test/another"
func main() {
// Issue 43990: this is not a link that most users can open from an LSP
// client: mongodb://not.a.link.com
println(pkg.Hello)
+ println(replace.Hello)
+ println(fixed.Hello)
+ println(another.Hello)
}`
const proxy = `
@@ -38,6 +61,32 @@ go 1.12
-- import.test@v1.2.3/pkg/const.go --
package pkg
+
+-- replace.test@v1.2.4/go.mod --
+module replace.test
+
+go 1.12
+-- replace.test@v1.2.4/replace/const.go --
+package replace
+
+const Hello = "Hello"
+
+-- replace.fixed.test@v1.2.4/go.mod --
+module replace.fixed.test
+
+go 1.12
+-- replace.fixed.test@v1.2.4/fixed/const.go --
+package fixed
+
+const Hello = "Hello"
+
+-- another.test@v1.2.3/go.mod --
+module another.test
+
+go 1.12
+-- another.test@v1.2.3/another/const.go --
+package another
+
const Hello = "Hello"
`
WithOptions(
@@ -47,25 +96,82 @@ const Hello = "Hello"
env.OpenFile("main.go")
env.OpenFile("go.mod")
- modLink := "https://pkg.go.dev/mod/import.test@v1.2.3"
- pkgLink := "https://pkg.go.dev/import.test@v1.2.3/pkg"
+ const (
+ modImportLink = "https://pkg.go.dev/mod/import.test@v1.2.3"
+ modReplaceLink = "https://pkg.go.dev/mod/replace.test@v1.2.4"
+ modReplaceFixedeLink = "https://pkg.go.dev/mod/replace.fixed.test@v1.2.4"
+ modAnotherLink = "https://pkg.go.dev/mod/another.test@v1.2.3"
+
+ pkgImportLink = "https://pkg.go.dev/import.test@v1.2.3/pkg"
+ pkgReplaceLink = "https://pkg.go.dev/replace.test@v1.2.4/replace"
+ pkgReplaceFixedLink = "https://pkg.go.dev/replace.fixed.test@v1.2.4/fixed"
+ pkgAnotherLink = "https://pkg.go.dev/another.test@v1.2.3/another"
+ )
// First, check that we get the expected links via hover and documentLink.
content, _ := env.Hover(env.RegexpSearch("main.go", "pkg.Hello"))
- if content == nil || !strings.Contains(content.Value, pkgLink) {
- t.Errorf("hover: got %v in main.go, want contains %q", content, pkgLink)
+ if content == nil || !strings.Contains(content.Value, pkgImportLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgImportLink)
+ }
+ content, _ = env.Hover(env.RegexpSearch("main.go", "replace.Hello"))
+ if content == nil || !strings.Contains(content.Value, pkgReplaceLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgReplaceLink)
+ }
+ content, _ = env.Hover(env.RegexpSearch("main.go", "fixed.Hello"))
+ if content == nil || !strings.Contains(content.Value, pkgReplaceFixedLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgReplaceFixedLink)
+ }
+ content, _ = env.Hover(env.RegexpSearch("main.go", "another.Hello"))
+ if content == nil || !strings.Contains(content.Value, pkgAnotherLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgAnotherLink)
}
+
content, _ = env.Hover(env.RegexpSearch("go.mod", "import.test"))
- if content == nil || !strings.Contains(content.Value, pkgLink) {
- t.Errorf("hover: got %v in go.mod, want contains %q", content, pkgLink)
+ if content == nil || !strings.Contains(content.Value, pkgImportLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgImportLink)
+ }
+ content, _ = env.Hover(env.RegexpSearch("go.mod", "replace.test"))
+ if content == nil || !strings.Contains(content.Value, pkgReplaceLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgReplaceLink)
+ }
+ content, _ = env.Hover(env.RegexpSearch("go.mod", "replace.fixed.test"))
+ if content == nil || !strings.Contains(content.Value, pkgReplaceFixedLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgReplaceFixedLink)
+ }
+ content, _ = env.Hover(env.RegexpSearch("go.mod", "replace.another.test"))
+ if content == nil || !strings.Contains(content.Value, pkgAnotherLink) {
+ t.Errorf("hover: got %v in main.go, want contains %q", content, pkgAnotherLink)
+ }
+
+ getLinks := func(links []protocol.DocumentLink) []string {
+ var got []string
+ for i := range links {
+ got = append(got, *links[i].Target)
+ }
+ return got
}
links := env.DocumentLink("main.go")
- if len(links) != 1 || *links[0].Target != pkgLink {
- t.Errorf("documentLink: got links %+v for main.go, want one link with target %q", links, pkgLink)
+ got, want := getLinks(links), []string{
+ pkgImportLink,
+ pkgReplaceLink,
+ pkgReplaceFixedLink,
+ pkgAnotherLink,
+ }
+ if !slices.Equal(got, want) {
+ t.Errorf("documentLink: got links %v for main.go, want links %v", got, want)
}
+
links = env.DocumentLink("go.mod")
- if len(links) != 1 || *links[0].Target != modLink {
- t.Errorf("documentLink: got links %+v for go.mod, want one link with target %q", links, modLink)
+ localReplacePath := filepath.Join(env.Sandbox.Workdir.RootURI().Path(), "go.mod")
+ got, want = getLinks(links), []string{
+ localReplacePath, localReplacePath,
+ modImportLink,
+ modReplaceLink,
+ modReplaceFixedeLink,
+ modAnotherLink,
+ }
+ if !slices.Equal(got, want) {
+ t.Errorf("documentLink: got links %v for go.mod, want links %v", got, want)
}
// Then change the environment to make these links private.
@@ -75,20 +181,33 @@ const Hello = "Hello"
// Finally, verify that the links are gone.
content, _ = env.Hover(env.RegexpSearch("main.go", "pkg.Hello"))
- if content == nil || strings.Contains(content.Value, pkgLink) {
- t.Errorf("hover: got %v in main.go, want non-empty hover without %q", content, pkgLink)
+ if content == nil || strings.Contains(content.Value, pkgImportLink) {
+ t.Errorf("hover: got %v in main.go, want non-empty hover without %q", content, pkgImportLink)
}
content, _ = env.Hover(env.RegexpSearch("go.mod", "import.test"))
- if content == nil || strings.Contains(content.Value, modLink) {
- t.Errorf("hover: got %v in go.mod, want contains %q", content, modLink)
+ if content == nil || strings.Contains(content.Value, modImportLink) {
+ t.Errorf("hover: got %v in go.mod, want contains %q", content, modImportLink)
}
+
links = env.DocumentLink("main.go")
- if len(links) != 0 {
- t.Errorf("documentLink: got %d document links for main.go, want 0\nlinks: %v", len(links), links)
+ got, want = getLinks(links), []string{
+ pkgReplaceLink,
+ pkgReplaceFixedLink,
+ pkgAnotherLink,
+ }
+ if !slices.Equal(got, want) {
+ t.Errorf("documentLink: got links %v for main.go, want links %v", got, want)
}
+
links = env.DocumentLink("go.mod")
- if len(links) != 0 {
- t.Errorf("documentLink: got %d document links for go.mod, want 0\nlinks: %v", len(links), links)
+ got, want = getLinks(links), []string{
+ localReplacePath, localReplacePath,
+ modReplaceLink,
+ modReplaceFixedeLink,
+ modAnotherLink,
+ }
+ if !slices.Equal(got, want) {
+ t.Errorf("documentLink: got links %v for go.mod, want links %v", got, want)
}
})
}
diff --git a/gopls/internal/test/integration/misc/modify_tags_test.go b/gopls/internal/test/integration/misc/modify_tags_test.go
new file mode 100644
index 00000000000..48b5f772ffb
--- /dev/null
+++ b/gopls/internal/test/integration/misc/modify_tags_test.go
@@ -0,0 +1,159 @@
+// Copyright 2025 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/gopls/internal/protocol"
+ "golang.org/x/tools/gopls/internal/protocol/command"
+ "golang.org/x/tools/gopls/internal/test/compare"
+ "golang.org/x/tools/gopls/internal/test/integration"
+)
+
+func TestModifyTags(t *testing.T) {
+ const files = `
+-- go.mod --
+module example.com
+
+go 1.20
+
+-- a.go --
+package a
+
+type A struct {
+ B string
+ C int
+ D bool
+ E string
+}
+
+-- b.go --
+package b
+
+type B struct {
+ B string ` + "`json:\"b,omitempty\"`" + `
+ C int ` + "`json:\"c,omitempty\"`" + `
+ D bool ` + "`json:\"d,omitempty\"`" + `
+ E string ` + "`json:\"e,omitempty\"`" + `
+}
+
+-- c.go --
+package c
+
+type C struct {
+ B string
+ C int
+ D bool ` + "`json:\"d,omitempty\"`" + `
+ E string
+}
+`
+
+ const wantAddTagsEntireStruct = `package a
+
+type A struct {
+ B string ` + "`json:\"b,omitempty\"`" + `
+ C int ` + "`json:\"c,omitempty\"`" + `
+ D bool ` + "`json:\"d,omitempty\"`" + `
+ E string ` + "`json:\"e,omitempty\"`" + `
+}
+`
+
+ const wantRemoveTags = `package b
+
+type B struct {
+ B string
+ C int
+ D bool ` + "`json:\"d,omitempty\"`" + `
+ E string ` + "`json:\"e,omitempty\"`" + `
+}
+`
+
+ const wantAddTagsSingleLine = `package a
+
+type A struct {
+ B string
+ C int
+ D bool ` + "`json:\"d,omitempty\"`" + `
+ E string
+}
+`
+
+ const wantRemoveOptions = `package c
+
+type C struct {
+ B string
+ C int
+ D bool ` + "`json:\"d\"`" + `
+ E string
+}
+`
+
+ tests := []struct {
+ file string
+ args command.ModifyTagsArgs
+ want string
+ }{
+ {file: "a.go", args: command.ModifyTagsArgs{
+ Range: protocol.Range{
+ Start: protocol.Position{Line: 2, Character: 0},
+ End: protocol.Position{Line: 8, Character: 0},
+ },
+ Add: "json",
+ AddOptions: "json=omitempty",
+ }, want: wantAddTagsEntireStruct},
+ {file: "b.go", args: command.ModifyTagsArgs{
+ Range: protocol.Range{
+ Start: protocol.Position{Line: 3, Character: 2},
+ End: protocol.Position{Line: 4, Character: 6},
+ },
+ Remove: "json",
+ }, want: wantRemoveTags},
+ {file: "a.go", args: command.ModifyTagsArgs{
+ Range: protocol.Range{
+ Start: protocol.Position{Line: 5, Character: 0},
+ End: protocol.Position{Line: 5, Character: 7},
+ },
+ Add: "json",
+ AddOptions: "json=omitempty",
+ }, want: wantAddTagsSingleLine},
+ {file: "c.go", args: command.ModifyTagsArgs{
+ Range: protocol.Range{
+ Start: protocol.Position{Line: 3, Character: 0},
+ End: protocol.Position{Line: 7, Character: 0},
+ },
+ RemoveOptions: "json=omitempty",
+ }, want: wantRemoveOptions},
+ }
+
+ for _, test := range tests {
+ integration.Run(t, files, func(t *testing.T, env *integration.Env) {
+ uri := env.Sandbox.Workdir.URI(test.file)
+ args, err := command.MarshalArgs(
+ command.ModifyTagsArgs{
+ URI: uri,
+ Range: test.args.Range,
+ Add: test.args.Add,
+ AddOptions: test.args.AddOptions,
+ Remove: test.args.Remove,
+ RemoveOptions: test.args.RemoveOptions,
+ },
+ )
+ if err != nil {
+ t.Fatal(err)
+ }
+ var res any
+ env.ExecuteCommand(&protocol.ExecuteCommandParams{
+ Command: command.ModifyTags.String(),
+ Arguments: args,
+ }, &res)
+ // Wait until we finish writing to the file.
+ env.AfterChange()
+ if got := env.BufferText(test.file); got != test.want {
+ t.Errorf("modify_tags returned unexpected diff (-want +got):\n%s", compare.Text(test.want, got))
+ }
+ })
+ }
+}
diff --git a/gopls/internal/test/integration/misc/prompt_test.go b/gopls/internal/test/integration/misc/prompt_test.go
index 37cd654b08d..21da1b5853f 100644
--- a/gopls/internal/test/integration/misc/prompt_test.go
+++ b/gopls/internal/test/integration/misc/prompt_test.go
@@ -429,7 +429,7 @@ func main() {
const maxPrompts = 5 // internal prompt limit defined by gopls
- for i := 0; i < maxPrompts+1; i++ {
+ for i := range maxPrompts + 1 {
WithOptions(
Modes(Default), // no need to run this in all modes
EnvVars{
diff --git a/gopls/internal/test/integration/misc/vuln_test.go b/gopls/internal/test/integration/misc/vuln_test.go
index 9dad13179af..47f4c6a77b7 100644
--- a/gopls/internal/test/integration/misc/vuln_test.go
+++ b/gopls/internal/test/integration/misc/vuln_test.go
@@ -912,7 +912,6 @@ func testVulnDiagnostics(t *testing.T, env *Env, pattern string, want vulnDiagEx
// Find the diagnostics at loc.start.
var diag *protocol.Diagnostic
for _, g := range got.Diagnostics {
- g := g
if g.Range.Start == loc.Range.Start && w.msg == g.Message {
modPathDiagnostics = append(modPathDiagnostics, g)
diag = &g
diff --git a/gopls/internal/test/integration/options.go b/gopls/internal/test/integration/options.go
index 11824aa7c16..176a8a64f24 100644
--- a/gopls/internal/test/integration/options.go
+++ b/gopls/internal/test/integration/options.go
@@ -5,6 +5,7 @@
package integration
import (
+ "maps"
"strings"
"testing"
"time"
@@ -115,9 +116,7 @@ func (s Settings) set(opts *runConfig) {
if opts.editor.Settings == nil {
opts.editor.Settings = make(map[string]any)
}
- for k, v := range s {
- opts.editor.Settings[k] = v
- }
+ maps.Copy(opts.editor.Settings, s)
}
// WorkspaceFolders configures the workdir-relative workspace folders or uri
@@ -135,6 +134,22 @@ func WorkspaceFolders(relFolders ...string) RunOption {
})
}
+// NoDefaultWorkspaceFiles is used to specify whether the fake editor
+// should give a default workspace folder to the LSP server.
+// When it's true, the editor will pass original WorkspaceFolders to the LSP server.
+func NoDefaultWorkspaceFiles() RunOption {
+ return optionSetter(func(opts *runConfig) {
+ opts.editor.NoDefaultWorkspaceFiles = true
+ })
+}
+
+// RootPath configures the roo path which will be converted to rootUri and sent to the LSP server.
+func RootPath(relpath string) RunOption {
+ return optionSetter(func(opts *runConfig) {
+ opts.editor.RelRootPath = relpath
+ })
+}
+
// FolderSettings defines per-folder workspace settings, keyed by relative path
// to the folder.
//
@@ -161,9 +176,7 @@ func (e EnvVars) set(opts *runConfig) {
if opts.editor.Env == nil {
opts.editor.Env = make(map[string]string)
}
- for k, v := range e {
- opts.editor.Env[k] = v
- }
+ maps.Copy(opts.editor.Env, e)
}
// FakeGoPackagesDriver configures gopls to run with a fake GOPACKAGESDRIVER
diff --git a/gopls/internal/test/integration/runner.go b/gopls/internal/test/integration/runner.go
index c4609cb8f91..8fdcc26af59 100644
--- a/gopls/internal/test/integration/runner.go
+++ b/gopls/internal/test/integration/runner.go
@@ -142,7 +142,6 @@ func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOptio
}
for _, tc := range tests {
- tc := tc
config := defaultConfig()
for _, opt := range opts {
opt.set(&config)
diff --git a/gopls/internal/test/integration/web/assembly_test.go b/gopls/internal/test/integration/web/assembly_test.go
new file mode 100644
index 00000000000..6820cbb7864
--- /dev/null
+++ b/gopls/internal/test/integration/web/assembly_test.go
@@ -0,0 +1,135 @@
+// Copyright 2025 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 web_test
+
+import (
+ "runtime"
+ "testing"
+
+ "golang.org/x/tools/gopls/internal/protocol"
+ "golang.org/x/tools/gopls/internal/protocol/command"
+ "golang.org/x/tools/gopls/internal/settings"
+ . "golang.org/x/tools/gopls/internal/test/integration"
+ "golang.org/x/tools/internal/testenv"
+)
+
+// TestAssembly is a basic test of the web-based assembly listing.
+func TestAssembly(t *testing.T) {
+ testenv.NeedsGoCommand1Point(t, 22) // for up-to-date assembly listing
+
+ const files = `
+-- go.mod --
+module example.com
+
+-- a/a.go --
+package a
+
+func f(x int) int {
+ println("hello")
+ defer println("world")
+ return x
+}
+
+func g() {
+ println("goodbye")
+}
+
+var v = [...]int{
+ f(123),
+ f(456),
+}
+
+func init() {
+ f(789)
+}
+`
+ Run(t, files, func(t *testing.T, env *Env) {
+ env.OpenFile("a/a.go")
+
+ asmFor := func(pattern string) []byte {
+ // Invoke the "Browse assembly" code action to start the server.
+ loc := env.RegexpSearch("a/a.go", pattern)
+ actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger)
+ if err != nil {
+ t.Fatalf("CodeAction: %v", err)
+ }
+ action, err := codeActionByKind(actions, settings.GoAssembly)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Execute the command.
+ // Its side effect should be a single showDocument request.
+ params := &protocol.ExecuteCommandParams{
+ Command: action.Command.Command,
+ Arguments: action.Command.Arguments,
+ }
+ var result command.DebuggingResult
+ collectDocs := env.Awaiter.ListenToShownDocuments()
+ env.ExecuteCommand(params, &result)
+ doc := shownDocument(t, collectDocs(), "http:")
+ if doc == nil {
+ t.Fatalf("no showDocument call had 'file:' prefix")
+ }
+ t.Log("showDocument(package doc) URL:", doc.URI)
+
+ return get(t, doc.URI)
+ }
+
+ // Get the report and do some minimal checks for sensible results.
+ //
+ // Use only portable instructions below! Remember that
+ // this is a test of plumbing, not compilation, so
+ // it's better to skip the tests, rather than refine
+ // them, on any architecture that gives us trouble
+ // (e.g. uses JAL for CALL, or BL for RET).
+ // We conservatively test only on the two most popular
+ // architectures.
+ {
+ report := asmFor("println")
+ checkMatch(t, true, report, `TEXT.*example.com/a.f`)
+ switch runtime.GOARCH {
+ case "amd64", "arm64":
+ checkMatch(t, true, report, `CALL runtime.printlock`)
+ checkMatch(t, true, report, `CALL runtime.printstring`)
+ checkMatch(t, true, report, `CALL runtime.printunlock`)
+ checkMatch(t, true, report, `CALL example.com/a.f.deferwrap`)
+ checkMatch(t, true, report, `RET`)
+ checkMatch(t, true, report, `CALL runtime.morestack_noctxt`)
+ }
+
+ // Nested functions are also shown.
+ //
+ // The condition here was relaxed to unblock go.dev/cl/639515.
+ checkMatch(t, true, report, `example.com/a.f.deferwrap`)
+
+ // But other functions are not.
+ checkMatch(t, false, report, `TEXT.*example.com/a.g`)
+ }
+
+ // Check that code in a package-level var initializer is found too.
+ {
+ report := asmFor(`f\(123\)`)
+ switch runtime.GOARCH {
+ case "amd64", "arm64":
+ checkMatch(t, true, report, `TEXT.*example.com/a.init`)
+ checkMatch(t, true, report, `MOV.? \$123`)
+ checkMatch(t, true, report, `MOV.? \$456`)
+ checkMatch(t, true, report, `CALL example.com/a.f`)
+ }
+ }
+
+ // And code in a source-level init function.
+ {
+ report := asmFor(`f\(789\)`)
+ switch runtime.GOARCH {
+ case "amd64", "arm64":
+ checkMatch(t, true, report, `TEXT.*example.com/a.init`)
+ checkMatch(t, true, report, `MOV.? \$789`)
+ checkMatch(t, true, report, `CALL example.com/a.f`)
+ }
+ }
+ })
+}
diff --git a/gopls/internal/test/integration/web/freesymbols_test.go b/gopls/internal/test/integration/web/freesymbols_test.go
new file mode 100644
index 00000000000..7f44c29ec1f
--- /dev/null
+++ b/gopls/internal/test/integration/web/freesymbols_test.go
@@ -0,0 +1,76 @@
+// Copyright 2025 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 web_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/gopls/internal/protocol"
+ "golang.org/x/tools/gopls/internal/protocol/command"
+ "golang.org/x/tools/gopls/internal/settings"
+ . "golang.org/x/tools/gopls/internal/test/integration"
+)
+
+// TestFreeSymbols is a basic test of interaction with the "free symbols" web report.
+func TestFreeSymbols(t *testing.T) {
+ const files = `
+-- go.mod --
+module example.com
+
+-- a/a.go --
+package a
+
+import "fmt"
+import "bytes"
+
+func f(buf bytes.Buffer, greeting string) {
+/* « */
+ fmt.Fprintf(&buf, "%s", greeting)
+ buf.WriteString(fmt.Sprint("foo"))
+ buf.WriteByte(0)
+/* » */
+ buf.Write(nil)
+}
+`
+ Run(t, files, func(t *testing.T, env *Env) {
+ env.OpenFile("a/a.go")
+
+ // Invoke the "Browse free symbols" code
+ // action to start the server.
+ loc := env.RegexpSearch("a/a.go", "«((?:.|\n)*)»")
+ actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger)
+ if err != nil {
+ t.Fatalf("CodeAction: %v", err)
+ }
+ action, err := codeActionByKind(actions, settings.GoFreeSymbols)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Execute the command.
+ // Its side effect should be a single showDocument request.
+ params := &protocol.ExecuteCommandParams{
+ Command: action.Command.Command,
+ Arguments: action.Command.Arguments,
+ }
+ var result command.DebuggingResult
+ collectDocs := env.Awaiter.ListenToShownDocuments()
+ env.ExecuteCommand(params, &result)
+ doc := shownDocument(t, collectDocs(), "http:")
+ if doc == nil {
+ t.Fatalf("no showDocument call had 'file:' prefix")
+ }
+ t.Log("showDocument(package doc) URL:", doc.URI)
+
+ // Get the report and do some minimal checks for sensible results.
+ report := get(t, doc.URI)
+ checkMatch(t, true, report, `