whatcanGOwrong
This commit is contained in:
+267
@@ -0,0 +1,267 @@
|
||||
// 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.
|
||||
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "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"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "deprecated",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "deprecated"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: checkDeprecated,
|
||||
FactTypes: []analysis.Fact{(*deprecationFact)(nil)},
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated",
|
||||
}
|
||||
|
||||
// checkDeprecated is a simplified copy of staticcheck.CheckDeprecated.
|
||||
func checkDeprecated(pass *analysis.Pass) (interface{}, error) {
|
||||
inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
deprs, err := collectDeprecatedNames(pass, inspector)
|
||||
if err != nil || (len(deprs.packages) == 0 && len(deprs.objects) == 0) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reportDeprecation := func(depr *deprecationFact, node ast.Node) {
|
||||
// TODO(hyangah): staticcheck.CheckDeprecated has more complex logic. Do we need it here?
|
||||
// TODO(hyangah): Scrub depr.Msg. depr.Msg may contain Go comments
|
||||
// markdown syntaxes but LSP diagnostics do not support markdown syntax.
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := format.Node(buf, pass.Fset, node); err != nil {
|
||||
// This shouldn't happen but let's be conservative.
|
||||
buf.Reset()
|
||||
buf.WriteString("declaration")
|
||||
}
|
||||
pass.ReportRangef(node, "%s is deprecated: %s", buf, depr.Msg)
|
||||
}
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.SelectorExpr)(nil)}
|
||||
inspector.Preorder(nodeFilter, func(node ast.Node) {
|
||||
// Caveat: this misses dot-imported objects
|
||||
sel, ok := node.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
obj := pass.TypesInfo.ObjectOf(sel.Sel)
|
||||
if fn, ok := obj.(*types.Func); ok {
|
||||
obj = fn.Origin()
|
||||
}
|
||||
if obj == nil || obj.Pkg() == nil {
|
||||
// skip invalid sel.Sel.
|
||||
return
|
||||
}
|
||||
|
||||
if obj.Pkg() == pass.Pkg {
|
||||
// A package is allowed to use its own deprecated objects
|
||||
return
|
||||
}
|
||||
|
||||
// A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main
|
||||
// generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo"
|
||||
// and "foo_test".
|
||||
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() {
|
||||
// foo_test (the external tests of foo) can use objects from foo.
|
||||
return
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() {
|
||||
// foo.test (the main package of foo's tests) can use objects from foo.
|
||||
return
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") {
|
||||
// foo.test (the main package of foo's tests) can use objects from foo's external tests.
|
||||
return
|
||||
}
|
||||
|
||||
if depr, ok := deprs.objects[obj]; ok {
|
||||
reportDeprecation(depr, sel)
|
||||
}
|
||||
})
|
||||
|
||||
for _, f := range pass.Files {
|
||||
for _, spec := range f.Imports {
|
||||
var imp *types.Package
|
||||
var obj types.Object
|
||||
if spec.Name != nil {
|
||||
obj = pass.TypesInfo.ObjectOf(spec.Name)
|
||||
} else {
|
||||
obj = pass.TypesInfo.Implicits[spec]
|
||||
}
|
||||
pkgName, ok := obj.(*types.PkgName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
imp = pkgName.Imported()
|
||||
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
pkgPath := pass.Pkg.Path()
|
||||
if strings.TrimSuffix(pkgPath, "_test") == path {
|
||||
// foo_test can import foo
|
||||
continue
|
||||
}
|
||||
if strings.TrimSuffix(pkgPath, ".test") == path {
|
||||
// foo.test can import foo
|
||||
continue
|
||||
}
|
||||
if strings.TrimSuffix(pkgPath, ".test") == strings.TrimSuffix(path, "_test") {
|
||||
// foo.test can import foo_test
|
||||
continue
|
||||
}
|
||||
if depr, ok := deprs.packages[imp]; ok {
|
||||
reportDeprecation(depr, spec.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type deprecationFact struct{ Msg string }
|
||||
|
||||
func (*deprecationFact) AFact() {}
|
||||
func (d *deprecationFact) String() string { return "Deprecated: " + d.Msg }
|
||||
|
||||
type deprecatedNames struct {
|
||||
objects map[types.Object]*deprecationFact
|
||||
packages map[*types.Package]*deprecationFact
|
||||
}
|
||||
|
||||
// collectDeprecatedNames collects deprecated identifiers and publishes
|
||||
// them both as Facts and the return value. This is a simplified copy
|
||||
// of staticcheck's fact_deprecated analyzer.
|
||||
func collectDeprecatedNames(pass *analysis.Pass, ins *inspector.Inspector) (deprecatedNames, error) {
|
||||
extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
|
||||
for _, doc := range docs {
|
||||
if doc == nil {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(doc.Text(), "\n\n")
|
||||
for _, part := range parts {
|
||||
if !strings.HasPrefix(part, "Deprecated: ") {
|
||||
continue
|
||||
}
|
||||
alt := part[len("Deprecated: "):]
|
||||
alt = strings.Replace(alt, "\n", " ", -1)
|
||||
return strings.TrimSpace(alt)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
doDocs := func(names []*ast.Ident, docs *ast.CommentGroup) {
|
||||
alt := extractDeprecatedMessage([]*ast.CommentGroup{docs})
|
||||
if alt == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
obj := pass.TypesInfo.ObjectOf(name)
|
||||
pass.ExportObjectFact(obj, &deprecationFact{alt})
|
||||
}
|
||||
}
|
||||
|
||||
var docs []*ast.CommentGroup
|
||||
for _, f := range pass.Files {
|
||||
docs = append(docs, f.Doc)
|
||||
}
|
||||
if alt := extractDeprecatedMessage(docs); alt != "" {
|
||||
// Don't mark package syscall as deprecated, even though
|
||||
// it is. A lot of people still use it for simple
|
||||
// constants like SIGKILL, and I am not comfortable
|
||||
// telling them to use x/sys for that.
|
||||
if pass.Pkg.Path() != "syscall" {
|
||||
pass.ExportPackageFact(&deprecationFact{alt})
|
||||
}
|
||||
}
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.GenDecl)(nil),
|
||||
(*ast.FuncDecl)(nil),
|
||||
(*ast.TypeSpec)(nil),
|
||||
(*ast.ValueSpec)(nil),
|
||||
(*ast.File)(nil),
|
||||
(*ast.StructType)(nil),
|
||||
(*ast.InterfaceType)(nil),
|
||||
}
|
||||
ins.Preorder(nodeFilter, func(node ast.Node) {
|
||||
var names []*ast.Ident
|
||||
var docs *ast.CommentGroup
|
||||
switch node := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch node.Tok {
|
||||
case token.TYPE, token.CONST, token.VAR:
|
||||
docs = node.Doc
|
||||
for i := range node.Specs {
|
||||
switch n := node.Specs[i].(type) {
|
||||
case *ast.ValueSpec:
|
||||
names = append(names, n.Names...)
|
||||
case *ast.TypeSpec:
|
||||
names = append(names, n.Name)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
docs = node.Doc
|
||||
names = []*ast.Ident{node.Name}
|
||||
case *ast.TypeSpec:
|
||||
docs = node.Doc
|
||||
names = []*ast.Ident{node.Name}
|
||||
case *ast.ValueSpec:
|
||||
docs = node.Doc
|
||||
names = node.Names
|
||||
case *ast.StructType:
|
||||
for _, field := range node.Fields.List {
|
||||
doDocs(field.Names, field.Doc)
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
for _, field := range node.Methods.List {
|
||||
doDocs(field.Names, field.Doc)
|
||||
}
|
||||
}
|
||||
if docs != nil && len(names) > 0 {
|
||||
doDocs(names, docs)
|
||||
}
|
||||
})
|
||||
|
||||
// Every identifier is potentially deprecated, so we will need
|
||||
// to look up facts a lot. Construct maps of all facts propagated
|
||||
// to this pass for fast lookup.
|
||||
out := deprecatedNames{
|
||||
objects: map[types.Object]*deprecationFact{},
|
||||
packages: map[*types.Package]*deprecationFact{},
|
||||
}
|
||||
for _, fact := range pass.AllObjectFacts() {
|
||||
out.objects[fact.Object] = fact.Fact.(*deprecationFact)
|
||||
}
|
||||
for _, fact := range pass.AllPackageFacts() {
|
||||
out.packages[fact.Package] = fact.Fact.(*deprecationFact)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// 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.
|
||||
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, Analyzer, "a")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.
|
||||
|
||||
// Package deprecated defines an Analyzer that marks deprecated symbols and package imports.
|
||||
//
|
||||
// # Analyzer deprecated
|
||||
//
|
||||
// deprecated: check for use of deprecated identifiers
|
||||
//
|
||||
// The deprecated analyzer looks for deprecated symbols and package
|
||||
// imports.
|
||||
//
|
||||
// See https://go.dev/wiki/Deprecated to learn about Go's convention
|
||||
// for documenting and signaling deprecated identifiers.
|
||||
package deprecated
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
package usedeprecated
|
||||
|
||||
import "io/ioutil" // want "\"io/ioutil\" is deprecated: .*"
|
||||
|
||||
func x() {
|
||||
_, _ = ioutil.ReadFile("") // want "ioutil.ReadFile is deprecated: As of Go 1.16, .*"
|
||||
Legacy() // expect no deprecation notice.
|
||||
}
|
||||
|
||||
// Legacy is deprecated.
|
||||
//
|
||||
// Deprecated: use X instead.
|
||||
func Legacy() {} // want Legacy:"Deprecated: use X instead."
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
package usedeprecated
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestF(t *testing.T) {
|
||||
Legacy() // expect no deprecation notice.
|
||||
x()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
// Package embeddirective defines an Analyzer that validates //go:embed directives.
|
||||
// The analyzer defers fixes to its parent golang.Analyzer.
|
||||
//
|
||||
// # Analyzer embed
|
||||
//
|
||||
// embed: check //go:embed directive usage
|
||||
//
|
||||
// This analyzer checks that the embed package is imported if //go:embed
|
||||
// directives are present, providing a suggested fix to add the import if
|
||||
// it is missing.
|
||||
//
|
||||
// This analyzer also checks that //go:embed directives precede the
|
||||
// declaration of a single variable.
|
||||
package embeddirective
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package embeddirective
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "embed",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "embed"),
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective",
|
||||
}
|
||||
|
||||
const FixCategory = "addembedimport" // recognized by gopls ApplyFix
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, f := range pass.Files {
|
||||
comments := embedDirectiveComments(f)
|
||||
if len(comments) == 0 {
|
||||
continue // nothing to check
|
||||
}
|
||||
|
||||
hasEmbedImport := false
|
||||
for _, imp := range f.Imports {
|
||||
if imp.Path.Value == `"embed"` {
|
||||
hasEmbedImport = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
pos, end := c.Pos(), c.Pos()+token.Pos(len("//go:embed"))
|
||||
|
||||
if !hasEmbedImport {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: `must import "embed" when using go:embed directives`,
|
||||
Category: FixCategory,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: `Add missing "embed" import`,
|
||||
// No TextEdits => computed by a gopls command.
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
var msg string
|
||||
spec := nextVarSpec(c, f)
|
||||
switch {
|
||||
case spec == nil:
|
||||
msg = `go:embed directives must precede a "var" declaration`
|
||||
case len(spec.Names) != 1:
|
||||
msg = "declarations following go:embed directives must define a single variable"
|
||||
case len(spec.Values) > 0:
|
||||
msg = "declarations following go:embed directives must not specify a value"
|
||||
case !embeddableType(pass.TypesInfo.Defs[spec.Names[0]]):
|
||||
msg = "declarations following go:embed directives must be of type string, []byte or embed.FS"
|
||||
}
|
||||
if msg != "" {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
End: end,
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// embedDirectiveComments returns all comments in f that contains a //go:embed directive.
|
||||
func embedDirectiveComments(f *ast.File) []*ast.Comment {
|
||||
comments := []*ast.Comment{}
|
||||
for _, cg := range f.Comments {
|
||||
for _, c := range cg.List {
|
||||
if strings.HasPrefix(c.Text, "//go:embed ") {
|
||||
comments = append(comments, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
// nextVarSpec returns the ValueSpec for the variable declaration immediately following
|
||||
// the go:embed comment, or nil if the next declaration is not a variable declaration.
|
||||
func nextVarSpec(com *ast.Comment, f *ast.File) *ast.ValueSpec {
|
||||
// Embed directives must be followed by a declaration of one variable with no value.
|
||||
// There may be comments and empty lines between the directive and the declaration.
|
||||
var nextDecl ast.Decl
|
||||
for _, d := range f.Decls {
|
||||
if com.End() < d.End() {
|
||||
nextDecl = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextDecl == nil || nextDecl.Pos() == token.NoPos {
|
||||
return nil
|
||||
}
|
||||
decl, ok := nextDecl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if decl.Tok != token.VAR {
|
||||
return nil
|
||||
}
|
||||
|
||||
// var declarations can be both freestanding and blocks (with parenthesis).
|
||||
// Only the first variable spec following the directive is interesting.
|
||||
var nextSpec ast.Spec
|
||||
for _, s := range decl.Specs {
|
||||
if com.End() < s.End() {
|
||||
nextSpec = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextSpec == nil {
|
||||
return nil
|
||||
}
|
||||
spec, ok := nextSpec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
// Invalid AST, but keep going.
|
||||
return nil
|
||||
}
|
||||
return spec
|
||||
}
|
||||
|
||||
// embeddableType in go:embed directives are string, []byte or embed.FS.
|
||||
func embeddableType(o types.Object) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// For embed.FS the underlying type is an implementation detail.
|
||||
// As long as the named type resolves to embed.FS, it is OK.
|
||||
if named, ok := aliases.Unalias(o.Type()).(*types.Named); ok {
|
||||
obj := named.Obj()
|
||||
if obj.Pkg() != nil && obj.Pkg().Path() == "embed" && obj.Name() == "FS" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch v := o.Type().Underlying().(type) {
|
||||
case *types.Basic:
|
||||
return types.Identical(v, types.Typ[types.String])
|
||||
case *types.Slice:
|
||||
return types.Identical(v.Elem(), types.Typ[types.Byte])
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package embeddirective
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, Analyzer, "a")
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Hello World
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package a
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:embed embedtext // want "must import \"embed\" when using go:embed directives"
|
||||
var s string
|
||||
|
||||
// This is main function
|
||||
func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package a
|
||||
|
||||
// Misplaced, above imports.
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
|
||||
import (
|
||||
"embed"
|
||||
embedPkg "embed"
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed embedText // ok
|
||||
var e1 string
|
||||
|
||||
// The analyzer does not check for many directives using the same var.
|
||||
//
|
||||
//go:embed embedText // ok
|
||||
//go:embed embedText // ok
|
||||
var e2 string
|
||||
|
||||
// Comments and blank lines between are OK. All types OK.
|
||||
//
|
||||
//go:embed embedText // ok
|
||||
//
|
||||
// foo
|
||||
|
||||
var e3 string
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e4 []byte
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e5 embed.FS
|
||||
|
||||
// Followed by wrong kind of decl.
|
||||
//
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
func fooFunc() {}
|
||||
|
||||
// Multiple variable specs.
|
||||
//
|
||||
//go:embed embedText // want "declarations following go:embed directives must define a single variable"
|
||||
var e6, e7 []byte
|
||||
|
||||
// Specifying a value is not allowed.
|
||||
//
|
||||
//go:embed embedText // want "declarations following go:embed directives must not specify a value"
|
||||
var e8 string = "foo"
|
||||
|
||||
// TODO: This should not be OK, misplaced according to compiler.
|
||||
//
|
||||
//go:embed embedText // ok
|
||||
var (
|
||||
e9 string
|
||||
e10 string
|
||||
)
|
||||
|
||||
// Type definition.
|
||||
type fooType []byte
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e11 fooType
|
||||
|
||||
// Type alias.
|
||||
type barType = string
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e12 barType
|
||||
|
||||
// Renamed embed package.
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e13 embedPkg.FS
|
||||
|
||||
// Renamed embed package alias.
|
||||
type embedAlias = embedPkg.FS
|
||||
|
||||
//go:embed embedText //ok
|
||||
var e14 embedAlias
|
||||
|
||||
// var blocks are OK as long as the variable following the directive is OK.
|
||||
var (
|
||||
x, y, z string
|
||||
//go:embed embedText // ok
|
||||
e20 string
|
||||
q, r, t string
|
||||
)
|
||||
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
var ()
|
||||
|
||||
// Incorrect types.
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e16 byte
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e17 []string
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e18 embed.Foo
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e19 foo.FS
|
||||
|
||||
type byteAlias byte
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e15 byteAlias
|
||||
|
||||
// A type declaration of embed.FS is not accepted by the compiler, in contrast to an alias.
|
||||
type embedDecl embed.FS
|
||||
|
||||
//go:embed embedText // want `declarations following go:embed directives must be of type string, \[\]byte or embed.FS`
|
||||
var e16 embedDecl
|
||||
|
||||
// This is main function
|
||||
func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
// No declaration following.
|
||||
//go:embed embedText // want "go:embed directives must precede a \"var\" declaration"
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package a
|
||||
|
||||
var (
|
||||
// Okay directive wise but the compiler will complain that
|
||||
// imports must appear before other declarations.
|
||||
//go:embed embedText // ok
|
||||
foo string
|
||||
)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
// This is main function
|
||||
func main() {
|
||||
fmt.Println(s)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.
|
||||
|
||||
// Package fillreturns defines an Analyzer that will attempt to
|
||||
// automatically fill in a return statement that has missing
|
||||
// values with zero value elements.
|
||||
//
|
||||
// # Analyzer fillreturns
|
||||
//
|
||||
// fillreturns: suggest fixes for errors due to an incorrect number of return values
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "wrong number of return values (want %d, got %d)". For example:
|
||||
//
|
||||
// func m() (int, string, *bool, error) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// will turn into
|
||||
//
|
||||
// func m() (int, string, *bool, error) {
|
||||
// return 0, "", nil, nil
|
||||
// }
|
||||
//
|
||||
// This functionality is similar to https://github.com/sqs/goreturns.
|
||||
package fillreturns
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
// Copyright 2020 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 fillreturns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/types"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
"golang.org/x/tools/internal/fuzzy"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "fillreturns",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "fillreturns"),
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
info := pass.TypesInfo
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("nil TypeInfo")
|
||||
}
|
||||
|
||||
outer:
|
||||
for _, typeErr := range pass.TypeErrors {
|
||||
// Filter out the errors that are not relevant to this analyzer.
|
||||
if !FixesError(typeErr) {
|
||||
continue
|
||||
}
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= typeErr.Pos && typeErr.Pos <= f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the end position of the error.
|
||||
// (This heuristic assumes that the buffer is formatted,
|
||||
// at least up to the end position of the error.)
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
typeErrEndPos := analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), typeErr.Pos)
|
||||
|
||||
// TODO(rfindley): much of the error handling code below returns, when it
|
||||
// should probably continue.
|
||||
|
||||
// Get the path for the relevant range.
|
||||
path, _ := astutil.PathEnclosingInterval(file, typeErr.Pos, typeErrEndPos)
|
||||
if len(path) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Find the enclosing return statement.
|
||||
var ret *ast.ReturnStmt
|
||||
var retIdx int
|
||||
for i, n := range path {
|
||||
if r, ok := n.(*ast.ReturnStmt); ok {
|
||||
ret = r
|
||||
retIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if ret == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get the function type that encloses the ReturnStmt.
|
||||
var enclosingFunc *ast.FuncType
|
||||
for _, n := range path[retIdx+1:] {
|
||||
switch node := n.(type) {
|
||||
case *ast.FuncLit:
|
||||
enclosingFunc = node.Type
|
||||
case *ast.FuncDecl:
|
||||
enclosingFunc = node.Type
|
||||
}
|
||||
if enclosingFunc != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if enclosingFunc == nil || enclosingFunc.Results == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip any generic enclosing functions, since type parameters don't
|
||||
// have 0 values.
|
||||
// TODO(rfindley): We should be able to handle this if the return
|
||||
// values are all concrete types.
|
||||
if tparams := enclosingFunc.TypeParams; tparams != nil && tparams.NumFields() > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Find the function declaration that encloses the ReturnStmt.
|
||||
var outer *ast.FuncDecl
|
||||
for _, p := range path {
|
||||
if p, ok := p.(*ast.FuncDecl); ok {
|
||||
outer = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if outer == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Skip any return statements that contain function calls with multiple
|
||||
// return values.
|
||||
for _, expr := range ret.Results {
|
||||
e, ok := expr.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if tup, ok := info.TypeOf(e).(*types.Tuple); ok && tup.Len() > 1 {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate the return values to track which values have been matched.
|
||||
remaining := make([]ast.Expr, len(ret.Results))
|
||||
copy(remaining, ret.Results)
|
||||
|
||||
fixed := make([]ast.Expr, len(enclosingFunc.Results.List))
|
||||
|
||||
// For each value in the return function declaration, find the leftmost element
|
||||
// in the return statement that has the desired type. If no such element exists,
|
||||
// fill in the missing value with the appropriate "zero" value.
|
||||
// Beware that type information may be incomplete.
|
||||
var retTyps []types.Type
|
||||
for _, ret := range enclosingFunc.Results.List {
|
||||
retTyp := info.TypeOf(ret.Type)
|
||||
if retTyp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
retTyps = append(retTyps, retTyp)
|
||||
}
|
||||
matches := analysisinternal.MatchingIdents(retTyps, file, ret.Pos(), info, pass.Pkg)
|
||||
for i, retTyp := range retTyps {
|
||||
var match ast.Expr
|
||||
var idx int
|
||||
for j, val := range remaining {
|
||||
if t := info.TypeOf(val); t == nil || !matchingTypes(t, retTyp) {
|
||||
continue
|
||||
}
|
||||
if !analysisinternal.IsZeroValue(val) {
|
||||
match, idx = val, j
|
||||
break
|
||||
}
|
||||
// If the current match is a "zero" value, we keep searching in
|
||||
// case we find a non-"zero" value match. If we do not find a
|
||||
// non-"zero" value, we will use the "zero" value.
|
||||
match, idx = val, j
|
||||
}
|
||||
|
||||
if match != nil {
|
||||
fixed[i] = match
|
||||
remaining = append(remaining[:idx], remaining[idx+1:]...)
|
||||
} else {
|
||||
names, ok := matches[retTyp]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid return type: %v", retTyp)
|
||||
}
|
||||
// Find the identifier most similar to the return type.
|
||||
// If no identifier matches the pattern, generate a zero value.
|
||||
if best := fuzzy.BestMatch(retTyp.String(), names); best != "" {
|
||||
fixed[i] = ast.NewIdent(best)
|
||||
} else if zero := analysisinternal.ZeroValue(file, pass.Pkg, retTyp); zero != nil {
|
||||
fixed[i] = zero
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any non-matching "zero values" from the leftover values.
|
||||
var nonZeroRemaining []ast.Expr
|
||||
for _, expr := range remaining {
|
||||
if !analysisinternal.IsZeroValue(expr) {
|
||||
nonZeroRemaining = append(nonZeroRemaining, expr)
|
||||
}
|
||||
}
|
||||
// Append leftover return values to end of new return statement.
|
||||
fixed = append(fixed, nonZeroRemaining...)
|
||||
|
||||
newRet := &ast.ReturnStmt{
|
||||
Return: ret.Pos(),
|
||||
Results: fixed,
|
||||
}
|
||||
|
||||
// Convert the new return statement AST to text.
|
||||
var newBuf bytes.Buffer
|
||||
if err := format.Node(&newBuf, pass.Fset, newRet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: typeErr.Pos,
|
||||
End: typeErrEndPos,
|
||||
Message: typeErr.Msg,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Fill in return values",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: ret.Pos(),
|
||||
End: ret.End(),
|
||||
NewText: newBuf.Bytes(),
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func matchingTypes(want, got types.Type) bool {
|
||||
if want == got || types.Identical(want, got) {
|
||||
return true
|
||||
}
|
||||
// Code segment to help check for untyped equality from (golang/go#32146).
|
||||
if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
|
||||
if lhs, ok := got.Underlying().(*types.Basic); ok {
|
||||
return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
|
||||
}
|
||||
}
|
||||
return types.AssignableTo(want, got) || types.ConvertibleTo(want, got)
|
||||
}
|
||||
|
||||
// Error messages have changed across Go versions. These regexps capture recent
|
||||
// incarnations.
|
||||
//
|
||||
// TODO(rfindley): once error codes are exported and exposed via go/packages,
|
||||
// use error codes rather than string matching here.
|
||||
var wrongReturnNumRegexes = []*regexp.Regexp{
|
||||
regexp.MustCompile(`wrong number of return values \(want (\d+), got (\d+)\)`),
|
||||
regexp.MustCompile(`too many return values`),
|
||||
regexp.MustCompile(`not enough return values`),
|
||||
}
|
||||
|
||||
func FixesError(err types.Error) bool {
|
||||
msg := strings.TrimSpace(err.Msg)
|
||||
for _, rx := range wrongReturnNumRegexes {
|
||||
if rx.MatchString(msg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2020 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 fillreturns_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/fillreturns"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
// TODO(golang/go#65294): update expectations and delete this
|
||||
// check once we update go.mod to go1.23 so that
|
||||
// gotypesalias=1 becomes the only supported mode.
|
||||
if aliases.Enabled() {
|
||||
t.Skip("expectations need updating for materialized aliases")
|
||||
}
|
||||
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, fillreturns.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 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 fillreturns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
ast2 "go/ast"
|
||||
"io"
|
||||
"net/http"
|
||||
. "net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type T struct{}
|
||||
type T1 = T
|
||||
type I interface{}
|
||||
type I1 = I
|
||||
type z func(string, http.Handler) error
|
||||
|
||||
func x() error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
|
||||
// The error messages below changed in 1.18; "return values" covers both forms.
|
||||
|
||||
func b() (string, int, error) {
|
||||
return "", errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func c() (string, int, error) {
|
||||
return 7, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func d() (string, int, error) {
|
||||
return "", 7 // want "return values"
|
||||
}
|
||||
|
||||
func e() (T, error, *bool) {
|
||||
return (z(http.ListenAndServe))("", nil) // want "return values"
|
||||
}
|
||||
|
||||
func preserveLeft() (int, int, error) {
|
||||
return 1, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func matchValues() (int, error, string) {
|
||||
return errors.New("foo"), 3 // want "return values"
|
||||
}
|
||||
|
||||
func preventDataOverwrite() (int, string) {
|
||||
return errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func closure() (string, error) {
|
||||
_ = func() (int, error) {
|
||||
return // want "return values"
|
||||
}
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func basic() (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, byte, rune, uint, int, uintptr, string, bool, error) {
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func complex() (*int, []int, [2]int, map[int]int) {
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func structsAndInterfaces() (T, url.URL, T1, I, I1, io.Reader, Client, ast2.Stmt) {
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func m() (int, error) {
|
||||
if 1 == 2 {
|
||||
return // want "return values"
|
||||
} else if 1 == 3 {
|
||||
return errors.New("foo") // want "return values"
|
||||
} else {
|
||||
return 1 // want "return values"
|
||||
}
|
||||
return // want "return values"
|
||||
}
|
||||
|
||||
func convertibleTypes() (ast2.Expr, int) {
|
||||
return &ast2.ArrayType{} // want "return values"
|
||||
}
|
||||
|
||||
func assignableTypes() (map[string]int, int) {
|
||||
type X map[string]int
|
||||
var x X
|
||||
return x // want "return values"
|
||||
}
|
||||
|
||||
func interfaceAndError() (I, int) {
|
||||
return errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func funcOneReturn() (string, error) {
|
||||
return strconv.Itoa(1) // want "return values"
|
||||
}
|
||||
|
||||
func funcMultipleReturn() (int, error, string) {
|
||||
return strconv.Atoi("1")
|
||||
}
|
||||
|
||||
func localFuncMultipleReturn() (string, int, error, string) {
|
||||
return b()
|
||||
}
|
||||
|
||||
func multipleUnused() (int, string, string, string) {
|
||||
return 3, 4, 5 // want "return values"
|
||||
}
|
||||
|
||||
func gotTooMany() int {
|
||||
if true {
|
||||
return 0, "" // want "return values"
|
||||
} else {
|
||||
return 1, 0, nil // want "return values"
|
||||
}
|
||||
return 0, 5, false // want "return values"
|
||||
}
|
||||
|
||||
func fillVars() (int, string, ast.Node, bool, error) {
|
||||
eint := 0
|
||||
s := "a"
|
||||
var t bool
|
||||
if true {
|
||||
err := errors.New("fail")
|
||||
return // want "return values"
|
||||
}
|
||||
n := ast.NewIdent("ident")
|
||||
int := 3
|
||||
var b bool
|
||||
return "" // want "return values"
|
||||
}
|
||||
go/pkg/mod/golang.org/x/tools/gopls@v0.16.2/internal/analysis/fillreturns/testdata/src/a/a.go.golden
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 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 fillreturns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
ast2 "go/ast"
|
||||
"io"
|
||||
"net/http"
|
||||
. "net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type T struct{}
|
||||
type T1 = T
|
||||
type I interface{}
|
||||
type I1 = I
|
||||
type z func(string, http.Handler) error
|
||||
|
||||
func x() error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
|
||||
// The error messages below changed in 1.18; "return values" covers both forms.
|
||||
|
||||
func b() (string, int, error) {
|
||||
return "", 0, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func c() (string, int, error) {
|
||||
return "", 7, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func d() (string, int, error) {
|
||||
return "", 7, nil // want "return values"
|
||||
}
|
||||
|
||||
func e() (T, error, *bool) {
|
||||
return T{}, (z(http.ListenAndServe))("", nil), nil // want "return values"
|
||||
}
|
||||
|
||||
func preserveLeft() (int, int, error) {
|
||||
return 1, 0, errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func matchValues() (int, error, string) {
|
||||
return 3, errors.New("foo"), "" // want "return values"
|
||||
}
|
||||
|
||||
func preventDataOverwrite() (int, string) {
|
||||
return 0, "", errors.New("foo") // want "return values"
|
||||
}
|
||||
|
||||
func closure() (string, error) {
|
||||
_ = func() (int, error) {
|
||||
return 0, nil // want "return values"
|
||||
}
|
||||
return "", nil // want "return values"
|
||||
}
|
||||
|
||||
func basic() (uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, complex64, complex128, byte, rune, uint, int, uintptr, string, bool, error) {
|
||||
return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", false, nil // want "return values"
|
||||
}
|
||||
|
||||
func complex() (*int, []int, [2]int, map[int]int) {
|
||||
return nil, nil, nil, nil // want "return values"
|
||||
}
|
||||
|
||||
func structsAndInterfaces() (T, url.URL, T1, I, I1, io.Reader, Client, ast2.Stmt) {
|
||||
return T{}, url.URL{}, T{}, nil, nil, nil, Client{}, nil // want "return values"
|
||||
}
|
||||
|
||||
func m() (int, error) {
|
||||
if 1 == 2 {
|
||||
return 0, nil // want "return values"
|
||||
} else if 1 == 3 {
|
||||
return 0, errors.New("foo") // want "return values"
|
||||
} else {
|
||||
return 1, nil // want "return values"
|
||||
}
|
||||
return 0, nil // want "return values"
|
||||
}
|
||||
|
||||
func convertibleTypes() (ast2.Expr, int) {
|
||||
return &ast2.ArrayType{}, 0 // want "return values"
|
||||
}
|
||||
|
||||
func assignableTypes() (map[string]int, int) {
|
||||
type X map[string]int
|
||||
var x X
|
||||
return x, 0 // want "return values"
|
||||
}
|
||||
|
||||
func interfaceAndError() (I, int) {
|
||||
return errors.New("foo"), 0 // want "return values"
|
||||
}
|
||||
|
||||
func funcOneReturn() (string, error) {
|
||||
return strconv.Itoa(1), nil // want "return values"
|
||||
}
|
||||
|
||||
func funcMultipleReturn() (int, error, string) {
|
||||
return strconv.Atoi("1")
|
||||
}
|
||||
|
||||
func localFuncMultipleReturn() (string, int, error, string) {
|
||||
return b()
|
||||
}
|
||||
|
||||
func multipleUnused() (int, string, string, string) {
|
||||
return 3, "", "", "", 4, 5 // want "return values"
|
||||
}
|
||||
|
||||
func gotTooMany() int {
|
||||
if true {
|
||||
return 0 // want "return values"
|
||||
} else {
|
||||
return 1 // want "return values"
|
||||
}
|
||||
return 5 // want "return values"
|
||||
}
|
||||
|
||||
func fillVars() (int, string, ast.Node, bool, error) {
|
||||
eint := 0
|
||||
s := "a"
|
||||
var t bool
|
||||
if true {
|
||||
err := errors.New("fail")
|
||||
return eint, s, nil, false, err // want "return values"
|
||||
}
|
||||
n := ast.NewIdent("ident")
|
||||
int := 3
|
||||
var b bool
|
||||
return int, "", n, b, nil // want "return values"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package fillreturns
|
||||
|
||||
func hello[T any]() int {
|
||||
return
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package fillreturns
|
||||
|
||||
func hello[T any]() int {
|
||||
return
|
||||
}
|
||||
+497
@@ -0,0 +1,497 @@
|
||||
// Copyright 2020 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 fillstruct defines an Analyzer that automatically
|
||||
// fills in a struct declaration with zero value elements for each field.
|
||||
//
|
||||
// The analyzer's diagnostic is merely a prompt.
|
||||
// The actual fix is created by a separate direct call from gopls to
|
||||
// the SuggestedFixes function.
|
||||
// Tests of Analyzer.Run can be found in ./testdata/src.
|
||||
// Tests of the SuggestedFixes logic live in ../../testdata/fillstruct.
|
||||
package fillstruct
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/gopls/internal/util/safetoken"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
"golang.org/x/tools/internal/fuzzy"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// Diagnose computes diagnostics for fillable struct literals overlapping with
|
||||
// the provided start and end position of file f.
|
||||
//
|
||||
// The diagnostic contains a lazy fix; the actual patch is computed
|
||||
// (via the ApplyFix command) by a call to [SuggestedFix].
|
||||
//
|
||||
// If either start or end is invalid, the entire file is inspected.
|
||||
func Diagnose(f *ast.File, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
|
||||
var diags []analysis.Diagnostic
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return true // pop
|
||||
}
|
||||
if start.IsValid() && n.End() < start || end.IsValid() && n.Pos() > end {
|
||||
return false // skip non-overlapping subtree
|
||||
}
|
||||
expr, ok := n.(*ast.CompositeLit)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
typ := info.TypeOf(expr)
|
||||
if typ == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Find reference to the type declaration of the struct being initialized.
|
||||
typ = typeparams.Deref(typ)
|
||||
tStruct, ok := typeparams.CoreType(typ).(*types.Struct)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
// Inv: typ is the possibly-named struct type.
|
||||
|
||||
fieldCount := tStruct.NumFields()
|
||||
|
||||
// Skip any struct that is already populated or that has no fields.
|
||||
if fieldCount == 0 || fieldCount == len(expr.Elts) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Are any fields in need of filling?
|
||||
var fillableFields []string
|
||||
for i := 0; i < fieldCount; i++ {
|
||||
field := tStruct.Field(i)
|
||||
// Ignore fields that are not accessible in the current package.
|
||||
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
|
||||
continue
|
||||
}
|
||||
fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String()))
|
||||
}
|
||||
if len(fillableFields) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Derive a name for the struct type.
|
||||
var name string
|
||||
if typ != tStruct {
|
||||
// named struct type (e.g. pkg.S[T])
|
||||
name = types.TypeString(typ, types.RelativeTo(pkg))
|
||||
} else {
|
||||
// anonymous struct type
|
||||
totalFields := len(fillableFields)
|
||||
const maxLen = 20
|
||||
// Find the index to cut off printing of fields.
|
||||
var i, fieldLen int
|
||||
for i = range fillableFields {
|
||||
if fieldLen > maxLen {
|
||||
break
|
||||
}
|
||||
fieldLen += len(fillableFields[i])
|
||||
}
|
||||
fillableFields = fillableFields[:i]
|
||||
if i < totalFields {
|
||||
fillableFields = append(fillableFields, "...")
|
||||
}
|
||||
name = fmt.Sprintf("anonymous struct{ %s }", strings.Join(fillableFields, ", "))
|
||||
}
|
||||
diags = append(diags, analysis.Diagnostic{
|
||||
Message: fmt.Sprintf("%s literal has missing fields", name),
|
||||
Pos: expr.Pos(),
|
||||
End: expr.End(),
|
||||
Category: FixCategory,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Fill %s", name),
|
||||
// No TextEdits => computed later by gopls.
|
||||
}},
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
const FixCategory = "fillstruct" // recognized by gopls ApplyFix
|
||||
|
||||
// SuggestedFix computes the suggested fix for the kinds of
|
||||
// diagnostics produced by the Analyzer above.
|
||||
func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
|
||||
if info == nil {
|
||||
return nil, nil, fmt.Errorf("nil types.Info")
|
||||
}
|
||||
|
||||
pos := start // don't use the end
|
||||
|
||||
// TODO(rstambler): Using ast.Inspect would probably be more efficient than
|
||||
// calling PathEnclosingInterval. Switch this approach.
|
||||
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
||||
if len(path) == 0 {
|
||||
return nil, nil, fmt.Errorf("no enclosing ast.Node")
|
||||
}
|
||||
var expr *ast.CompositeLit
|
||||
for _, n := range path {
|
||||
if node, ok := n.(*ast.CompositeLit); ok {
|
||||
expr = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
typ := info.TypeOf(expr)
|
||||
if typ == nil {
|
||||
return nil, nil, fmt.Errorf("no composite literal")
|
||||
}
|
||||
|
||||
// Find reference to the type declaration of the struct being initialized.
|
||||
typ = typeparams.Deref(typ)
|
||||
tStruct, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("%s is not a (pointer to) struct type",
|
||||
types.TypeString(typ, types.RelativeTo(pkg)))
|
||||
}
|
||||
// Inv: typ is the possibly-named struct type.
|
||||
|
||||
fieldCount := tStruct.NumFields()
|
||||
|
||||
// Check which types have already been filled in. (we only want to fill in
|
||||
// the unfilled types, or else we'll blat user-supplied details)
|
||||
prefilledFields := map[string]ast.Expr{}
|
||||
for _, e := range expr.Elts {
|
||||
if kv, ok := e.(*ast.KeyValueExpr); ok {
|
||||
if key, ok := kv.Key.(*ast.Ident); ok {
|
||||
prefilledFields[key.Name] = kv.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a new fileset to build up a token.File for the new composite
|
||||
// literal. We need one line for foo{, one line for }, and one line for
|
||||
// each field we're going to set. format.Node only cares about line
|
||||
// numbers, so we don't need to set columns, and each line can be
|
||||
// 1 byte long.
|
||||
// TODO(adonovan): why is this necessary? The position information
|
||||
// is going to be wrong for the existing trees in prefilledFields.
|
||||
// Can't the formatter just do its best with an empty fileset?
|
||||
fakeFset := token.NewFileSet()
|
||||
tok := fakeFset.AddFile("", -1, fieldCount+2)
|
||||
|
||||
line := 2 // account for 1-based lines and the left brace
|
||||
var fieldTyps []types.Type
|
||||
for i := 0; i < fieldCount; i++ {
|
||||
field := tStruct.Field(i)
|
||||
// Ignore fields that are not accessible in the current package.
|
||||
if field.Pkg() != nil && field.Pkg() != pkg && !field.Exported() {
|
||||
fieldTyps = append(fieldTyps, nil)
|
||||
continue
|
||||
}
|
||||
fieldTyps = append(fieldTyps, field.Type())
|
||||
}
|
||||
matches := analysisinternal.MatchingIdents(fieldTyps, file, start, info, pkg)
|
||||
var elts []ast.Expr
|
||||
for i, fieldTyp := range fieldTyps {
|
||||
if fieldTyp == nil {
|
||||
continue // TODO(adonovan): is this reachable?
|
||||
}
|
||||
fieldName := tStruct.Field(i).Name()
|
||||
|
||||
tok.AddLine(line - 1) // add 1 byte per line
|
||||
if line > tok.LineCount() {
|
||||
panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
|
||||
}
|
||||
pos := tok.LineStart(line)
|
||||
|
||||
kv := &ast.KeyValueExpr{
|
||||
Key: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: fieldName,
|
||||
},
|
||||
Colon: pos,
|
||||
}
|
||||
if expr, ok := prefilledFields[fieldName]; ok {
|
||||
kv.Value = expr
|
||||
} else {
|
||||
names, ok := matches[fieldTyp]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
|
||||
}
|
||||
|
||||
// Find the name most similar to the field name.
|
||||
// If no name matches the pattern, generate a zero value.
|
||||
// NOTE: We currently match on the name of the field key rather than the field type.
|
||||
if best := fuzzy.BestMatch(fieldName, names); best != "" {
|
||||
kv.Value = ast.NewIdent(best)
|
||||
} else if v := populateValue(file, pkg, fieldTyp); v != nil {
|
||||
kv.Value = v
|
||||
} else {
|
||||
return nil, nil, nil // no fix to suggest
|
||||
}
|
||||
}
|
||||
elts = append(elts, kv)
|
||||
line++
|
||||
}
|
||||
|
||||
// If all of the struct's fields are unexported, we have nothing to do.
|
||||
if len(elts) == 0 {
|
||||
return nil, nil, fmt.Errorf("no elements to fill")
|
||||
}
|
||||
|
||||
// Add the final line for the right brace. Offset is the number of
|
||||
// bytes already added plus 1.
|
||||
tok.AddLine(len(elts) + 1)
|
||||
line = len(elts) + 2
|
||||
if line > tok.LineCount() {
|
||||
panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
|
||||
}
|
||||
|
||||
cl := &ast.CompositeLit{
|
||||
Type: expr.Type,
|
||||
Lbrace: tok.LineStart(1),
|
||||
Elts: elts,
|
||||
Rbrace: tok.LineStart(line),
|
||||
}
|
||||
|
||||
// Find the line on which the composite literal is declared.
|
||||
split := bytes.Split(content, []byte("\n"))
|
||||
lineNumber := safetoken.StartPosition(fset, expr.Lbrace).Line
|
||||
firstLine := split[lineNumber-1] // lines are 1-indexed
|
||||
|
||||
// Trim the whitespace from the left of the line, and use the index
|
||||
// to get the amount of whitespace on the left.
|
||||
trimmed := bytes.TrimLeftFunc(firstLine, unicode.IsSpace)
|
||||
index := bytes.Index(firstLine, trimmed)
|
||||
whitespace := firstLine[:index]
|
||||
|
||||
// First pass through the formatter: turn the expr into a string.
|
||||
var formatBuf bytes.Buffer
|
||||
if err := format.Node(&formatBuf, fakeFset, cl); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to run first format on:\n%s\ngot err: %v", cl.Type, err)
|
||||
}
|
||||
sug := indent(formatBuf.Bytes(), whitespace)
|
||||
|
||||
if len(prefilledFields) > 0 {
|
||||
// Attempt a second pass through the formatter to line up columns.
|
||||
sourced, err := format.Source(sug)
|
||||
if err == nil {
|
||||
sug = indent(sourced, whitespace)
|
||||
}
|
||||
}
|
||||
|
||||
return fset, &analysis.SuggestedFix{
|
||||
TextEdits: []analysis.TextEdit{
|
||||
{
|
||||
Pos: expr.Pos(),
|
||||
End: expr.End(),
|
||||
NewText: sug,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// indent works line by line through str, indenting (prefixing) each line with
|
||||
// ind.
|
||||
func indent(str, ind []byte) []byte {
|
||||
split := bytes.Split(str, []byte("\n"))
|
||||
newText := bytes.NewBuffer(nil)
|
||||
for i, s := range split {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
// Don't add the extra indentation to the first line.
|
||||
if i != 0 {
|
||||
newText.Write(ind)
|
||||
}
|
||||
newText.Write(s)
|
||||
if i < len(split)-1 {
|
||||
newText.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return newText.Bytes()
|
||||
}
|
||||
|
||||
// populateValue constructs an expression to fill the value of a struct field.
|
||||
//
|
||||
// When the type of a struct field is a basic literal or interface, we return
|
||||
// default values. For other types, such as maps, slices, and channels, we create
|
||||
// empty expressions such as []T{} or make(chan T) rather than using default values.
|
||||
//
|
||||
// The reasoning here is that users will call fillstruct with the intention of
|
||||
// initializing the struct, in which case setting these fields to nil has no effect.
|
||||
//
|
||||
// populateValue returns nil if the value cannot be filled.
|
||||
func populateValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
|
||||
switch u := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch {
|
||||
case u.Info()&types.IsNumeric != 0:
|
||||
return &ast.BasicLit{Kind: token.INT, Value: "0"}
|
||||
case u.Info()&types.IsBoolean != 0:
|
||||
return &ast.Ident{Name: "false"}
|
||||
case u.Info()&types.IsString != 0:
|
||||
return &ast.BasicLit{Kind: token.STRING, Value: `""`}
|
||||
case u.Kind() == types.UnsafePointer:
|
||||
return ast.NewIdent("nil")
|
||||
case u.Kind() == types.Invalid:
|
||||
return nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown basic type %v", u))
|
||||
}
|
||||
|
||||
case *types.Map:
|
||||
k := analysisinternal.TypeExpr(f, pkg, u.Key())
|
||||
v := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if k == nil || v == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: &ast.MapType{
|
||||
Key: k,
|
||||
Value: v,
|
||||
},
|
||||
}
|
||||
case *types.Slice:
|
||||
s := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: &ast.ArrayType{
|
||||
Elt: s,
|
||||
},
|
||||
}
|
||||
|
||||
case *types.Array:
|
||||
a := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: &ast.ArrayType{
|
||||
Elt: a,
|
||||
Len: &ast.BasicLit{
|
||||
Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
case *types.Chan:
|
||||
v := analysisinternal.TypeExpr(f, pkg, u.Elem())
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
dir := ast.ChanDir(u.Dir())
|
||||
if u.Dir() == types.SendRecv {
|
||||
dir = ast.SEND | ast.RECV
|
||||
}
|
||||
return &ast.CallExpr{
|
||||
Fun: ast.NewIdent("make"),
|
||||
Args: []ast.Expr{
|
||||
&ast.ChanType{
|
||||
Dir: dir,
|
||||
Value: v,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
case *types.Struct:
|
||||
s := analysisinternal.TypeExpr(f, pkg, typ)
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.CompositeLit{
|
||||
Type: s,
|
||||
}
|
||||
|
||||
case *types.Signature:
|
||||
var params []*ast.Field
|
||||
for i := 0; i < u.Params().Len(); i++ {
|
||||
p := analysisinternal.TypeExpr(f, pkg, u.Params().At(i).Type())
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
params = append(params, &ast.Field{
|
||||
Type: p,
|
||||
Names: []*ast.Ident{
|
||||
{
|
||||
Name: u.Params().At(i).Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
var returns []*ast.Field
|
||||
for i := 0; i < u.Results().Len(); i++ {
|
||||
r := analysisinternal.TypeExpr(f, pkg, u.Results().At(i).Type())
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
returns = append(returns, &ast.Field{
|
||||
Type: r,
|
||||
})
|
||||
}
|
||||
return &ast.FuncLit{
|
||||
Type: &ast.FuncType{
|
||||
Params: &ast.FieldList{
|
||||
List: params,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: returns,
|
||||
},
|
||||
},
|
||||
Body: &ast.BlockStmt{},
|
||||
}
|
||||
|
||||
case *types.Pointer:
|
||||
switch aliases.Unalias(u.Elem()).(type) {
|
||||
case *types.Basic:
|
||||
return &ast.CallExpr{
|
||||
Fun: &ast.Ident{
|
||||
Name: "new",
|
||||
},
|
||||
Args: []ast.Expr{
|
||||
&ast.Ident{
|
||||
Name: u.Elem().String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
x := populateValue(f, pkg, u.Elem())
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.UnaryExpr{
|
||||
Op: token.AND,
|
||||
X: x,
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Interface:
|
||||
if param, ok := aliases.Unalias(typ).(*types.TypeParam); ok {
|
||||
// *new(T) is the zero value of a type parameter T.
|
||||
// TODO(adonovan): one could give a more specific zero
|
||||
// value if the type has a core type that is, say,
|
||||
// always a number or a pointer. See go/ssa for details.
|
||||
return &ast.StarExpr{
|
||||
X: &ast.CallExpr{
|
||||
Fun: ast.NewIdent("new"),
|
||||
Args: []ast.Expr{
|
||||
ast.NewIdent(param.Obj().Name()),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return ast.NewIdent("nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 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 fillstruct_test
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/fillstruct"
|
||||
)
|
||||
|
||||
// analyzer allows us to test the fillstruct code action using the analysistest
|
||||
// harness. (fillstruct used to be a gopls analyzer.)
|
||||
var analyzer = &analysis.Analyzer{
|
||||
Name: "fillstruct",
|
||||
Doc: "test only",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
for _, diag := range fillstruct.Diagnose(f, token.NoPos, token.NoPos, pass.Pkg, pass.TypesInfo) {
|
||||
pass.Report(diag)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillstruct",
|
||||
RunDespiteErrors: true,
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
// Copyright 2020 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 fillstruct
|
||||
|
||||
import (
|
||||
data "b"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type emptyStruct struct{}
|
||||
|
||||
var _ = emptyStruct{}
|
||||
|
||||
type basicStruct struct {
|
||||
foo int
|
||||
}
|
||||
|
||||
var _ = basicStruct{} // want `basicStruct literal has missing fields`
|
||||
|
||||
type twoArgStruct struct {
|
||||
foo int
|
||||
bar string
|
||||
}
|
||||
|
||||
var _ = twoArgStruct{} // want `twoArgStruct literal has missing fields`
|
||||
|
||||
var _ = twoArgStruct{ // want `twoArgStruct literal has missing fields`
|
||||
bar: "bar",
|
||||
}
|
||||
|
||||
type nestedStruct struct {
|
||||
bar string
|
||||
basic basicStruct
|
||||
}
|
||||
|
||||
var _ = nestedStruct{} // want `nestedStruct literal has missing fields`
|
||||
|
||||
var _ = data.B{} // want `b.B literal has missing fields`
|
||||
|
||||
type typedStruct struct {
|
||||
m map[string]int
|
||||
s []int
|
||||
c chan int
|
||||
c1 <-chan int
|
||||
a [2]string
|
||||
}
|
||||
|
||||
var _ = typedStruct{} // want `typedStruct literal has missing fields`
|
||||
|
||||
type funStruct struct {
|
||||
fn func(i int) int
|
||||
}
|
||||
|
||||
var _ = funStruct{} // want `funStruct literal has missing fields`
|
||||
|
||||
type funStructComplex struct {
|
||||
fn func(i int, s string) (string, int)
|
||||
}
|
||||
|
||||
var _ = funStructComplex{} // want `funStructComplex literal has missing fields`
|
||||
|
||||
type funStructEmpty struct {
|
||||
fn func()
|
||||
}
|
||||
|
||||
var _ = funStructEmpty{} // want `funStructEmpty literal has missing fields`
|
||||
|
||||
type Foo struct {
|
||||
A int
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
X *Foo
|
||||
Y *Foo
|
||||
}
|
||||
|
||||
var _ = Bar{} // want `Bar literal has missing fields`
|
||||
|
||||
type importedStruct struct {
|
||||
m map[*ast.CompositeLit]ast.Field
|
||||
s []ast.BadExpr
|
||||
a [3]token.Token
|
||||
c chan ast.EmptyStmt
|
||||
fn func(ast_decl ast.DeclStmt) ast.Ellipsis
|
||||
st ast.CompositeLit
|
||||
}
|
||||
|
||||
var _ = importedStruct{} // want `importedStruct literal has missing fields`
|
||||
|
||||
type pointerBuiltinStruct struct {
|
||||
b *bool
|
||||
s *string
|
||||
i *int
|
||||
}
|
||||
|
||||
var _ = pointerBuiltinStruct{} // want `pointerBuiltinStruct literal has missing fields`
|
||||
|
||||
var _ = []ast.BasicLit{
|
||||
{}, // want `go/ast.BasicLit literal has missing fields`
|
||||
}
|
||||
|
||||
var _ = []ast.BasicLit{{}} // want "go/ast.BasicLit literal has missing fields"
|
||||
|
||||
type unsafeStruct struct {
|
||||
foo unsafe.Pointer
|
||||
}
|
||||
|
||||
var _ = unsafeStruct{} // want `unsafeStruct literal has missing fields`
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
package fillstruct
|
||||
|
||||
type B struct {
|
||||
ExportedInt int
|
||||
unexportedInt int
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 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 fillstruct
|
||||
|
||||
type emptyStruct[A any] struct{}
|
||||
|
||||
var _ = emptyStruct[int]{}
|
||||
|
||||
type basicStruct[T any] struct {
|
||||
foo T
|
||||
}
|
||||
|
||||
var _ = basicStruct[int]{} // want `basicStruct\[int\] literal has missing fields`
|
||||
|
||||
type twoArgStruct[F, B any] struct {
|
||||
foo F
|
||||
bar B
|
||||
}
|
||||
|
||||
var _ = twoArgStruct[string, int]{} // want `twoArgStruct\[string, int\] literal has missing fields`
|
||||
|
||||
var _ = twoArgStruct[int, string]{ // want `twoArgStruct\[int, string\] literal has missing fields`
|
||||
bar: "bar",
|
||||
}
|
||||
|
||||
type nestedStruct struct {
|
||||
bar string
|
||||
basic basicStruct[int]
|
||||
}
|
||||
|
||||
var _ = nestedStruct{} // want "nestedStruct literal has missing fields"
|
||||
|
||||
func _[T any]() {
|
||||
type S struct{ t T }
|
||||
x := S{} // want "S"
|
||||
_ = x
|
||||
}
|
||||
|
||||
func Test() {
|
||||
var tests = []struct {
|
||||
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p string
|
||||
}{
|
||||
{}, // want "anonymous struct{ a: string, b: string, c: string, ... } literal has missing fields"
|
||||
}
|
||||
for _, test := range tests {
|
||||
_ = test
|
||||
}
|
||||
}
|
||||
|
||||
func _[T twoArgStruct[int, int]]() {
|
||||
_ = T{} // want "T literal has missing fields"
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2024 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 fillswitch identifies switches with missing cases.
|
||||
//
|
||||
// It reports a diagnostic for each type switch or 'enum' switch that
|
||||
// has missing cases, and suggests a fix to fill them in.
|
||||
//
|
||||
// The possible cases are: for a type switch, each accessible named
|
||||
// type T or pointer *T that is assignable to the interface type; and
|
||||
// for an 'enum' switch, each accessible named constant of the same
|
||||
// type as the switch value.
|
||||
//
|
||||
// For an 'enum' switch, it will suggest cases for all possible values of the
|
||||
// type.
|
||||
//
|
||||
// type Suit int8
|
||||
// const (
|
||||
// Spades Suit = iota
|
||||
// Hearts
|
||||
// Diamonds
|
||||
// Clubs
|
||||
// )
|
||||
//
|
||||
// var s Suit
|
||||
// switch s {
|
||||
// case Spades:
|
||||
// }
|
||||
//
|
||||
// It will report a diagnostic with a suggested fix to fill in the remaining
|
||||
// cases:
|
||||
//
|
||||
// var s Suit
|
||||
// switch s {
|
||||
// case Spades:
|
||||
// case Hearts:
|
||||
// case Diamonds:
|
||||
// case Clubs:
|
||||
// default:
|
||||
// panic(fmt.Sprintf("unexpected Suit: %v", s))
|
||||
// }
|
||||
//
|
||||
// For a type switch, it will suggest cases for all types that implement the
|
||||
// interface.
|
||||
//
|
||||
// var stmt ast.Stmt
|
||||
// switch stmt.(type) {
|
||||
// case *ast.IfStmt:
|
||||
// }
|
||||
//
|
||||
// It will report a diagnostic with a suggested fix to fill in the remaining
|
||||
// cases:
|
||||
//
|
||||
// var stmt ast.Stmt
|
||||
// switch stmt.(type) {
|
||||
// case *ast.IfStmt:
|
||||
// case *ast.ForStmt:
|
||||
// case *ast.RangeStmt:
|
||||
// case *ast.AssignStmt:
|
||||
// case *ast.GoStmt:
|
||||
// ...
|
||||
// default:
|
||||
// panic(fmt.Sprintf("unexpected ast.Stmt: %T", stmt))
|
||||
// }
|
||||
package fillswitch
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
// Copyright 2024 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 fillswitch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// Diagnose computes diagnostics for switch statements with missing cases
|
||||
// overlapping with the provided start and end position of file f.
|
||||
//
|
||||
// If either start or end is invalid, the entire file is inspected.
|
||||
func Diagnose(f *ast.File, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
|
||||
var diags []analysis.Diagnostic
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return true // pop
|
||||
}
|
||||
if start.IsValid() && n.End() < start ||
|
||||
end.IsValid() && n.Pos() > end {
|
||||
return false // skip non-overlapping subtree
|
||||
}
|
||||
var fix *analysis.SuggestedFix
|
||||
switch n := n.(type) {
|
||||
case *ast.SwitchStmt:
|
||||
fix = suggestedFixSwitch(n, pkg, info)
|
||||
case *ast.TypeSwitchStmt:
|
||||
fix = suggestedFixTypeSwitch(n, pkg, info)
|
||||
}
|
||||
if fix != nil {
|
||||
diags = append(diags, analysis.Diagnostic{
|
||||
Message: fix.Message,
|
||||
Pos: n.Pos(),
|
||||
End: n.Pos() + token.Pos(len("switch")),
|
||||
SuggestedFixes: []analysis.SuggestedFix{*fix},
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func suggestedFixTypeSwitch(stmt *ast.TypeSwitchStmt, pkg *types.Package, info *types.Info) *analysis.SuggestedFix {
|
||||
if hasDefaultCase(stmt.Body) {
|
||||
return nil
|
||||
}
|
||||
|
||||
namedType := namedTypeFromTypeSwitch(stmt, info)
|
||||
if namedType == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
existingCases := caseTypes(stmt.Body, info)
|
||||
// Gather accessible package-level concrete types
|
||||
// that implement the switch interface type.
|
||||
scope := namedType.Obj().Pkg().Scope()
|
||||
var buf bytes.Buffer
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
if tname, ok := obj.(*types.TypeName); !ok || tname.IsAlias() {
|
||||
continue // not a defined type
|
||||
}
|
||||
|
||||
if types.IsInterface(obj.Type()) {
|
||||
continue
|
||||
}
|
||||
|
||||
samePkg := obj.Pkg() == pkg
|
||||
if !samePkg && !obj.Exported() {
|
||||
continue // inaccessible
|
||||
}
|
||||
|
||||
var key caseType
|
||||
if types.AssignableTo(obj.Type(), namedType.Obj().Type()) {
|
||||
key.named = obj.Type().(*types.Named)
|
||||
} else if ptr := types.NewPointer(obj.Type()); types.AssignableTo(ptr, namedType.Obj().Type()) {
|
||||
key.named = obj.Type().(*types.Named)
|
||||
key.ptr = true
|
||||
}
|
||||
|
||||
if key.named != nil {
|
||||
if existingCases[key] {
|
||||
continue
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString("\t")
|
||||
}
|
||||
|
||||
buf.WriteString("case ")
|
||||
if key.ptr {
|
||||
buf.WriteByte('*')
|
||||
}
|
||||
|
||||
if p := key.named.Obj().Pkg(); p != pkg {
|
||||
// TODO: use the correct package name when the import is renamed
|
||||
buf.WriteString(p.Name())
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(key.named.Obj().Name())
|
||||
buf.WriteString(":\n")
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch assign := stmt.Assign.(type) {
|
||||
case *ast.AssignStmt:
|
||||
addDefaultCase(&buf, namedType, assign.Lhs[0])
|
||||
case *ast.ExprStmt:
|
||||
if assert, ok := assign.X.(*ast.TypeAssertExpr); ok {
|
||||
addDefaultCase(&buf, namedType, assert.X)
|
||||
}
|
||||
}
|
||||
|
||||
return &analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("Add cases for %s", namedType.Obj().Name()),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: stmt.End() - token.Pos(len("}")),
|
||||
End: stmt.End() - token.Pos(len("}")),
|
||||
NewText: buf.Bytes(),
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func suggestedFixSwitch(stmt *ast.SwitchStmt, pkg *types.Package, info *types.Info) *analysis.SuggestedFix {
|
||||
if hasDefaultCase(stmt.Body) {
|
||||
return nil
|
||||
}
|
||||
|
||||
namedType, ok := info.TypeOf(stmt.Tag).(*types.Named)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
existingCases := caseConsts(stmt.Body, info)
|
||||
// Gather accessible named constants of the same type as the switch value.
|
||||
scope := namedType.Obj().Pkg().Scope()
|
||||
var buf bytes.Buffer
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
if c, ok := obj.(*types.Const); ok &&
|
||||
(obj.Pkg() == pkg || obj.Exported()) && // accessible
|
||||
types.Identical(obj.Type(), namedType.Obj().Type()) &&
|
||||
!existingCases[c] {
|
||||
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString("\t")
|
||||
}
|
||||
|
||||
buf.WriteString("case ")
|
||||
if c.Pkg() != pkg {
|
||||
buf.WriteString(c.Pkg().Name())
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(c.Name())
|
||||
buf.WriteString(":\n")
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
addDefaultCase(&buf, namedType, stmt.Tag)
|
||||
|
||||
return &analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("Add cases for %s", namedType.Obj().Name()),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: stmt.End() - token.Pos(len("}")),
|
||||
End: stmt.End() - token.Pos(len("}")),
|
||||
NewText: buf.Bytes(),
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func addDefaultCase(buf *bytes.Buffer, named *types.Named, expr ast.Expr) {
|
||||
var dottedBuf bytes.Buffer
|
||||
// writeDotted emits a dotted path a.b.c.
|
||||
var writeDotted func(e ast.Expr) bool
|
||||
writeDotted = func(e ast.Expr) bool {
|
||||
switch e := e.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
if !writeDotted(e.X) {
|
||||
return false
|
||||
}
|
||||
dottedBuf.WriteByte('.')
|
||||
dottedBuf.WriteString(e.Sel.Name)
|
||||
return true
|
||||
case *ast.Ident:
|
||||
dottedBuf.WriteString(e.Name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
buf.WriteString("\tdefault:\n")
|
||||
typeName := fmt.Sprintf("%s.%s", named.Obj().Pkg().Name(), named.Obj().Name())
|
||||
if writeDotted(expr) {
|
||||
// Switch tag expression is a dotted path.
|
||||
// It is safe to re-evaluate it in the default case.
|
||||
format := fmt.Sprintf("unexpected %s: %%#v", typeName)
|
||||
fmt.Fprintf(buf, "\t\tpanic(fmt.Sprintf(%q, %s))\n\t", format, dottedBuf.String())
|
||||
} else {
|
||||
// Emit simpler message, without re-evaluating tag expression.
|
||||
fmt.Fprintf(buf, "\t\tpanic(%q)\n\t", "unexpected "+typeName)
|
||||
}
|
||||
}
|
||||
|
||||
func namedTypeFromTypeSwitch(stmt *ast.TypeSwitchStmt, info *types.Info) *types.Named {
|
||||
switch assign := stmt.Assign.(type) {
|
||||
case *ast.ExprStmt:
|
||||
if typ, ok := assign.X.(*ast.TypeAssertExpr); ok {
|
||||
if named, ok := info.TypeOf(typ.X).(*types.Named); ok {
|
||||
return named
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.AssignStmt:
|
||||
if typ, ok := assign.Rhs[0].(*ast.TypeAssertExpr); ok {
|
||||
if named, ok := info.TypeOf(typ.X).(*types.Named); ok {
|
||||
return named
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasDefaultCase(body *ast.BlockStmt) bool {
|
||||
for _, clause := range body.List {
|
||||
if len(clause.(*ast.CaseClause).List) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func caseConsts(body *ast.BlockStmt, info *types.Info) map[*types.Const]bool {
|
||||
out := map[*types.Const]bool{}
|
||||
for _, stmt := range body.List {
|
||||
for _, e := range stmt.(*ast.CaseClause).List {
|
||||
if info.Types[e].Value == nil {
|
||||
continue // not a constant
|
||||
}
|
||||
|
||||
if sel, ok := e.(*ast.SelectorExpr); ok {
|
||||
e = sel.Sel // replace pkg.C with C
|
||||
}
|
||||
|
||||
if e, ok := e.(*ast.Ident); ok {
|
||||
if c, ok := info.Uses[e].(*types.Const); ok {
|
||||
out[c] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type caseType struct {
|
||||
named *types.Named
|
||||
ptr bool
|
||||
}
|
||||
|
||||
func caseTypes(body *ast.BlockStmt, info *types.Info) map[caseType]bool {
|
||||
out := map[caseType]bool{}
|
||||
for _, stmt := range body.List {
|
||||
for _, e := range stmt.(*ast.CaseClause).List {
|
||||
if tv, ok := info.Types[e]; ok && tv.IsType() {
|
||||
t := tv.Type
|
||||
ptr := false
|
||||
if p, ok := t.(*types.Pointer); ok {
|
||||
t = p.Elem()
|
||||
ptr = true
|
||||
}
|
||||
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
out[caseType{named, ptr}] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 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 fillswitch_test
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/fillswitch"
|
||||
)
|
||||
|
||||
// analyzer allows us to test the fillswitch code action using the analysistest
|
||||
// harness.
|
||||
var analyzer = &analysis.Analyzer{
|
||||
Name: "fillswitch",
|
||||
Doc: "test only",
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
for _, diag := range fillswitch.Diagnose(f, token.NoPos, token.NoPos, pass.Pkg, pass.TypesInfo) {
|
||||
pass.Report(diag)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillswitch",
|
||||
RunDespiteErrors: true,
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, analyzer, "a")
|
||||
}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2020 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 fillswitch
|
||||
|
||||
import (
|
||||
data "b"
|
||||
)
|
||||
|
||||
type typeA int
|
||||
|
||||
const (
|
||||
typeAOne typeA = iota
|
||||
typeATwo
|
||||
typeAThree
|
||||
)
|
||||
|
||||
func doSwitch() {
|
||||
var a typeA
|
||||
switch a { // want `Add cases for typeA`
|
||||
}
|
||||
|
||||
switch a { // want `Add cases for typeA`
|
||||
case typeAOne:
|
||||
}
|
||||
|
||||
switch a {
|
||||
case typeAOne:
|
||||
default:
|
||||
}
|
||||
|
||||
switch a {
|
||||
case typeAOne:
|
||||
case typeATwo:
|
||||
case typeAThree:
|
||||
}
|
||||
|
||||
var b data.TypeB
|
||||
switch b { // want `Add cases for TypeB`
|
||||
case data.TypeBOne:
|
||||
}
|
||||
}
|
||||
|
||||
type notification interface {
|
||||
isNotification()
|
||||
}
|
||||
|
||||
type notificationOne struct{}
|
||||
|
||||
func (notificationOne) isNotification() {}
|
||||
|
||||
type notificationTwo struct{}
|
||||
|
||||
func (notificationTwo) isNotification() {}
|
||||
|
||||
func doTypeSwitch() {
|
||||
var not notification
|
||||
switch not.(type) { // want `Add cases for notification`
|
||||
}
|
||||
|
||||
switch not.(type) { // want `Add cases for notification`
|
||||
case notificationOne:
|
||||
}
|
||||
|
||||
switch not.(type) {
|
||||
case notificationOne:
|
||||
case notificationTwo:
|
||||
}
|
||||
|
||||
switch not.(type) {
|
||||
default:
|
||||
}
|
||||
|
||||
var t data.ExportedInterface
|
||||
switch t {
|
||||
}
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 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 fillswitch
|
||||
|
||||
type TypeB int
|
||||
|
||||
const (
|
||||
TypeBOne TypeB = iota
|
||||
TypeBTwo
|
||||
TypeBThree
|
||||
)
|
||||
|
||||
type ExportedInterface interface {
|
||||
isExportedInterface()
|
||||
}
|
||||
|
||||
type notExportedType struct{}
|
||||
|
||||
func (notExportedType) isExportedInterface() {}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// Copyright 2021 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 infertypeargs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"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/typeparams"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
const Doc = `check for unnecessary type arguments in call expressions
|
||||
|
||||
Explicit type arguments may be omitted from call expressions if they can be
|
||||
inferred from function arguments, or from other type arguments:
|
||||
|
||||
func f[T any](T) {}
|
||||
|
||||
func _() {
|
||||
f[string]("foo") // string could be inferred
|
||||
}
|
||||
`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "infertypeargs",
|
||||
Doc: Doc,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
for _, diag := range diagnose(pass.Fset, inspect, token.NoPos, token.NoPos, pass.Pkg, pass.TypesInfo) {
|
||||
pass.Report(diag)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Diagnose reports diagnostics describing simplifications to type
|
||||
// arguments overlapping with the provided start and end position.
|
||||
//
|
||||
// If start or end is token.NoPos, the corresponding bound is not checked
|
||||
// (i.e. if both start and end are NoPos, all call expressions are considered).
|
||||
func diagnose(fset *token.FileSet, inspect *inspector.Inspector, start, end token.Pos, pkg *types.Package, info *types.Info) []analysis.Diagnostic {
|
||||
var diags []analysis.Diagnostic
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.CallExpr)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(node ast.Node) {
|
||||
call := node.(*ast.CallExpr)
|
||||
x, lbrack, indices, rbrack := typeparams.UnpackIndexExpr(call.Fun)
|
||||
ident := calledIdent(x)
|
||||
if ident == nil || len(indices) == 0 {
|
||||
return // no explicit args, nothing to do
|
||||
}
|
||||
|
||||
if (start.IsValid() && call.End() < start) || (end.IsValid() && call.Pos() > end) {
|
||||
return // non-overlapping
|
||||
}
|
||||
|
||||
// Confirm that instantiation actually occurred at this ident.
|
||||
idata, ok := info.Instances[ident]
|
||||
if !ok {
|
||||
return // something went wrong, but fail open
|
||||
}
|
||||
instance := idata.Type
|
||||
|
||||
// Start removing argument expressions from the right, and check if we can
|
||||
// still infer the call expression.
|
||||
required := len(indices) // number of type expressions that are required
|
||||
for i := len(indices) - 1; i >= 0; i-- {
|
||||
var fun ast.Expr
|
||||
if i == 0 {
|
||||
// No longer an index expression: just use the parameterized operand.
|
||||
fun = x
|
||||
} else {
|
||||
fun = typeparams.PackIndexExpr(x, lbrack, indices[:i], indices[i-1].End())
|
||||
}
|
||||
newCall := &ast.CallExpr{
|
||||
Fun: fun,
|
||||
Lparen: call.Lparen,
|
||||
Args: call.Args,
|
||||
Ellipsis: call.Ellipsis,
|
||||
Rparen: call.Rparen,
|
||||
}
|
||||
info := &types.Info{
|
||||
Instances: make(map[*ast.Ident]types.Instance),
|
||||
}
|
||||
versions.InitFileVersions(info)
|
||||
if err := types.CheckExpr(fset, pkg, call.Pos(), newCall, info); err != nil {
|
||||
// Most likely inference failed.
|
||||
break
|
||||
}
|
||||
newIData := info.Instances[ident]
|
||||
newInstance := newIData.Type
|
||||
if !types.Identical(instance, newInstance) {
|
||||
// The inferred result type does not match the original result type, so
|
||||
// this simplification is not valid.
|
||||
break
|
||||
}
|
||||
required = i
|
||||
}
|
||||
if required < len(indices) {
|
||||
var s, e token.Pos
|
||||
var edit analysis.TextEdit
|
||||
if required == 0 {
|
||||
s, e = lbrack, rbrack+1 // erase the entire index
|
||||
edit = analysis.TextEdit{Pos: s, End: e}
|
||||
} else {
|
||||
s = indices[required].Pos()
|
||||
e = rbrack
|
||||
// erase from end of last arg to include last comma & white-spaces
|
||||
edit = analysis.TextEdit{Pos: indices[required-1].End(), End: e}
|
||||
}
|
||||
// Recheck that our (narrower) fixes overlap with the requested range.
|
||||
if (start.IsValid() && e < start) || (end.IsValid() && s > end) {
|
||||
return // non-overlapping
|
||||
}
|
||||
diags = append(diags, analysis.Diagnostic{
|
||||
Pos: s,
|
||||
End: e,
|
||||
Message: "unnecessary type arguments",
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Simplify type arguments",
|
||||
TextEdits: []analysis.TextEdit{edit},
|
||||
}},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func calledIdent(x ast.Expr) *ast.Ident {
|
||||
switch x := x.(type) {
|
||||
case *ast.Ident:
|
||||
return x
|
||||
case *ast.SelectorExpr:
|
||||
return x.Sel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 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 infertypeargs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/infertypeargs"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, infertypeargs.Analyzer, "a")
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2021 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 infertyepargs checker.
|
||||
|
||||
package a
|
||||
|
||||
func f[T any](T) {}
|
||||
|
||||
func g[T any]() T { var x T; return x }
|
||||
|
||||
func h[P interface{ ~*T }, T any]() {}
|
||||
|
||||
func _() {
|
||||
f[string]("hello") // want "unnecessary type arguments"
|
||||
f[int](2) // want "unnecessary type arguments"
|
||||
_ = g[int]()
|
||||
h[*int, int]() // want "unnecessary type arguments"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2021 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 infertyepargs checker.
|
||||
|
||||
package a
|
||||
|
||||
func f[T any](T) {}
|
||||
|
||||
func g[T any]() T { var x T; return x }
|
||||
|
||||
func h[P interface{ ~*T }, T any]() {}
|
||||
|
||||
func _() {
|
||||
f("hello") // want "unnecessary type arguments"
|
||||
f(2) // want "unnecessary type arguments"
|
||||
_ = g[int]()
|
||||
h[*int]() // want "unnecessary type arguments"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 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 a
|
||||
|
||||
import "a/imported"
|
||||
|
||||
func _() {
|
||||
var x int
|
||||
imported.F[int](x) // want "unnecessary type arguments"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 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 a
|
||||
|
||||
import "a/imported"
|
||||
|
||||
func _() {
|
||||
var x int
|
||||
imported.F(x) // want "unnecessary type arguments"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Copyright 2021 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 imported
|
||||
|
||||
func F[T any](T) {}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
// We should not suggest removing type arguments if doing so would change the
|
||||
// resulting type.
|
||||
|
||||
package a
|
||||
|
||||
func id[T any](t T) T { return t }
|
||||
|
||||
var _ = id[int](1) // want "unnecessary type arguments"
|
||||
var _ = id[string]("foo") // want "unnecessary type arguments"
|
||||
var _ = id[int64](2)
|
||||
|
||||
func pair[T any](t T) (T, T) { return t, t }
|
||||
|
||||
var _, _ = pair[int](3) // want "unnecessary type arguments"
|
||||
var _, _ = pair[int64](3)
|
||||
|
||||
func noreturn[T any](t T) {}
|
||||
|
||||
func _() {
|
||||
noreturn[int64](4)
|
||||
noreturn[int](4) // want "unnecessary type arguments"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
// We should not suggest removing type arguments if doing so would change the
|
||||
// resulting type.
|
||||
|
||||
package a
|
||||
|
||||
func id[T any](t T) T { return t }
|
||||
|
||||
var _ = id(1) // want "unnecessary type arguments"
|
||||
var _ = id("foo") // want "unnecessary type arguments"
|
||||
var _ = id[int64](2)
|
||||
|
||||
func pair[T any](t T) (T, T) { return t, t }
|
||||
|
||||
var _, _ = pair(3) // want "unnecessary type arguments"
|
||||
var _, _ = pair[int64](3)
|
||||
|
||||
func noreturn[T any](t T) {}
|
||||
|
||||
func _() {
|
||||
noreturn[int64](4)
|
||||
noreturn(4) // want "unnecessary type arguments"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 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 nonewvars defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "no new variables on left side of :=".
|
||||
//
|
||||
// # Analyzer nonewvars
|
||||
//
|
||||
// nonewvars: suggested fixes for "no new vars on left side of :="
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "no new vars on left side of :=". For example:
|
||||
//
|
||||
// z := 1
|
||||
// z := 2
|
||||
//
|
||||
// will turn into
|
||||
//
|
||||
// z := 1
|
||||
// z = 2
|
||||
package nonewvars
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2020 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 nonewvars defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "no new variables on left side of :=".
|
||||
package nonewvars
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "nonewvars",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "nonewvars"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
if len(pass.TypeErrors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
assignStmt, _ := n.(*ast.AssignStmt)
|
||||
// We only care about ":=".
|
||||
if assignStmt.Tok != token.DEFINE {
|
||||
return
|
||||
}
|
||||
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= assignStmt.Pos() && assignStmt.Pos() < f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, err := range pass.TypeErrors {
|
||||
if !FixesError(err.Msg) {
|
||||
continue
|
||||
}
|
||||
if assignStmt.Pos() > err.Pos || err.Pos >= assignStmt.End() {
|
||||
continue
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: err.Pos,
|
||||
End: analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos),
|
||||
Message: err.Msg,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Change ':=' to '='",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: err.Pos,
|
||||
End: err.Pos + 1,
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func FixesError(msg string) bool {
|
||||
return msg == "no new variables on left side of :="
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 nonewvars_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/nonewvars"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, nonewvars.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 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 nonewvars
|
||||
|
||||
import "log"
|
||||
|
||||
func x() {
|
||||
z := 1
|
||||
z := 2 // want "no new variables on left side of :="
|
||||
|
||||
_, z := 3, 100 // want "no new variables on left side of :="
|
||||
|
||||
log.Println(z)
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 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 nonewvars
|
||||
|
||||
import "log"
|
||||
|
||||
func x() {
|
||||
z := 1
|
||||
z = 2 // want "no new variables on left side of :="
|
||||
|
||||
_, z = 3, 100 // want "no new variables on left side of :="
|
||||
|
||||
log.Println(z)
|
||||
}
|
||||
go/pkg/mod/golang.org/x/tools/gopls@v0.16.2/internal/analysis/nonewvars/testdata/src/typeparams/a.go
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
package nonewvars
|
||||
|
||||
func hello[T any]() int {
|
||||
var z T
|
||||
z := 1 // want "no new variables on left side of :="
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package nonewvars
|
||||
|
||||
func hello[T any]() int {
|
||||
var z T
|
||||
z = 1 // want "no new variables on left side of :="
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2024 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 norangeoverfunc
|
||||
|
||||
// TODO(adonovan): delete this when #67237 and dominikh/go-tools#1494 are fixed.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "norangeoverfunc",
|
||||
Doc: `norangeoverfunc fails if a package uses go1.23 range-over-func
|
||||
|
||||
Require it from any analyzer that cannot yet safely process this new feature.`,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
filter := []ast.Node{(*ast.RangeStmt)(nil)}
|
||||
|
||||
// TODO(adonovan): opt: short circuit if not using go1.23.
|
||||
|
||||
var found *ast.RangeStmt
|
||||
inspect.Preorder(filter, func(n ast.Node) {
|
||||
if found == nil {
|
||||
stmt := n.(*ast.RangeStmt)
|
||||
if _, ok := pass.TypesInfo.TypeOf(stmt.X).Underlying().(*types.Signature); ok {
|
||||
found = stmt
|
||||
}
|
||||
}
|
||||
})
|
||||
if found != nil {
|
||||
return nil, fmt.Errorf("package %q uses go1.23 range-over-func; cannot build SSA or IR (#67237)",
|
||||
pass.Pkg.Path())
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
// Package noresultvalues defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "no result values expected".
|
||||
//
|
||||
// # Analyzer noresultvalues
|
||||
//
|
||||
// noresultvalues: suggested fixes for unexpected return values
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "no result values expected" or "too many return values".
|
||||
// For example:
|
||||
//
|
||||
// func z() { return nil }
|
||||
//
|
||||
// will turn into
|
||||
//
|
||||
// func z() { return }
|
||||
package noresultvalues
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 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 noresultvalues
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"strings"
|
||||
|
||||
_ "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"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "noresultvalues",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "noresultvalues"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
if len(pass.TypeErrors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodeFilter := []ast.Node{(*ast.ReturnStmt)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
retStmt, _ := n.(*ast.ReturnStmt)
|
||||
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= retStmt.Pos() && retStmt.Pos() < f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, err := range pass.TypeErrors {
|
||||
if !FixesError(err.Msg) {
|
||||
continue
|
||||
}
|
||||
if retStmt.Pos() >= err.Pos || err.Pos >= retStmt.End() {
|
||||
continue
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: err.Pos,
|
||||
End: analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos),
|
||||
Message: err.Msg,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: "Delete return values",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: retStmt.Pos(),
|
||||
End: retStmt.End(),
|
||||
NewText: []byte("return"),
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func FixesError(msg string) bool {
|
||||
return msg == "no result values expected" ||
|
||||
strings.HasPrefix(msg, "too many return values") && strings.Contains(msg, "want ()")
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 noresultvalues_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/noresultvalues"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, noresultvalues.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 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 noresultvalues
|
||||
|
||||
func x() { return nil } // want `no result values expected|too many return values`
|
||||
|
||||
func y() { return nil, "hello" } // want `no result values expected|too many return values`
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 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 noresultvalues
|
||||
|
||||
func x() { return } // want `no result values expected|too many return values`
|
||||
|
||||
func y() { return } // want `no result values expected|too many return values`
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package noresult
|
||||
|
||||
func hello[T any]() {
|
||||
var z T
|
||||
return z // want `no result values expected|too many return values`
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package noresult
|
||||
|
||||
func hello[T any]() {
|
||||
var z T
|
||||
return // want `no result values expected|too many return values`
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
// Package simplifycompositelit defines an Analyzer that simplifies composite literals.
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
//
|
||||
// # Analyzer simplifycompositelit
|
||||
//
|
||||
// simplifycompositelit: check for composite literal simplifications
|
||||
//
|
||||
// An array, slice, or map composite literal of the form:
|
||||
//
|
||||
// []T{T{}, T{}}
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// []T{{}, {}}
|
||||
//
|
||||
// This is one of the simplifications that "gofmt -s" applies.
|
||||
package simplifycompositelit
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// Copyright 2020 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 simplifycompositelit defines an Analyzer that simplifies composite literals.
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
package simplifycompositelit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"reflect"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "simplifycompositelit",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "simplifycompositelit"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
expr := n.(*ast.CompositeLit)
|
||||
|
||||
outer := expr
|
||||
var keyType, eltType ast.Expr
|
||||
switch typ := outer.Type.(type) {
|
||||
case *ast.ArrayType:
|
||||
eltType = typ.Elt
|
||||
case *ast.MapType:
|
||||
keyType = typ.Key
|
||||
eltType = typ.Value
|
||||
}
|
||||
|
||||
if eltType == nil {
|
||||
return
|
||||
}
|
||||
var ktyp reflect.Value
|
||||
if keyType != nil {
|
||||
ktyp = reflect.ValueOf(keyType)
|
||||
}
|
||||
typ := reflect.ValueOf(eltType)
|
||||
for _, x := range outer.Elts {
|
||||
// look at value of indexed/named elements
|
||||
if t, ok := x.(*ast.KeyValueExpr); ok {
|
||||
if keyType != nil {
|
||||
simplifyLiteral(pass, ktyp, keyType, t.Key)
|
||||
}
|
||||
x = t.Value
|
||||
}
|
||||
simplifyLiteral(pass, typ, eltType, x)
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) {
|
||||
// if the element is a composite literal and its literal type
|
||||
// matches the outer literal's element type exactly, the inner
|
||||
// literal type may be omitted
|
||||
if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) {
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, pass.Fset, inner.Type)
|
||||
createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String())
|
||||
}
|
||||
// if the outer literal's element type is a pointer type *T
|
||||
// and the element is & of a composite literal of type T,
|
||||
// the inner &T may be omitted.
|
||||
if ptr, ok := astType.(*ast.StarExpr); ok {
|
||||
if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
|
||||
if inner, ok := addr.X.(*ast.CompositeLit); ok {
|
||||
if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, pass.Fset, inner.Type)
|
||||
// Account for the & by subtracting 1 from typ.Pos().
|
||||
createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createDiagnostic(pass *analysis.Pass, start, end token.Pos, typ string) {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: start,
|
||||
End: end,
|
||||
Message: "redundant type from array, slice, or map composite literal",
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Remove '%s'", typ),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: start,
|
||||
End: end,
|
||||
NewText: []byte{},
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// match reports whether pattern matches val,
|
||||
// recording wildcard submatches in m.
|
||||
// If m == nil, match checks whether pattern == val.
|
||||
// from https://github.com/golang/go/blob/26154f31ad6c801d8bad5ef58df1e9263c6beec7/src/cmd/gofmt/rewrite.go#L160
|
||||
func match(pattern, val reflect.Value) bool {
|
||||
// Otherwise, pattern and val must match recursively.
|
||||
if !pattern.IsValid() || !val.IsValid() {
|
||||
return !pattern.IsValid() && !val.IsValid()
|
||||
}
|
||||
if pattern.Type() != val.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Special cases.
|
||||
switch pattern.Type() {
|
||||
case identType:
|
||||
// For identifiers, only the names need to match
|
||||
// (and none of the other *ast.Object information).
|
||||
// This is a common case, handle it all here instead
|
||||
// of recursing down any further via reflection.
|
||||
p := pattern.Interface().(*ast.Ident)
|
||||
v := val.Interface().(*ast.Ident)
|
||||
return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name
|
||||
case objectPtrType, positionType:
|
||||
// object pointers and token positions always match
|
||||
return true
|
||||
case callExprType:
|
||||
// For calls, the Ellipsis fields (token.Position) must
|
||||
// match since that is how f(x) and f(x...) are different.
|
||||
// Check them here but fall through for the remaining fields.
|
||||
p := pattern.Interface().(*ast.CallExpr)
|
||||
v := val.Interface().(*ast.CallExpr)
|
||||
if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
p := reflect.Indirect(pattern)
|
||||
v := reflect.Indirect(val)
|
||||
if !p.IsValid() || !v.IsValid() {
|
||||
return !p.IsValid() && !v.IsValid()
|
||||
}
|
||||
|
||||
switch p.Kind() {
|
||||
case reflect.Slice:
|
||||
if p.Len() != v.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < p.Len(); i++ {
|
||||
if !match(p.Index(i), v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Struct:
|
||||
for i := 0; i < p.NumField(); i++ {
|
||||
if !match(p.Field(i), v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Interface:
|
||||
return match(p.Elem(), v.Elem())
|
||||
}
|
||||
|
||||
// Handle token integers, etc.
|
||||
return p.Interface() == v.Interface()
|
||||
}
|
||||
|
||||
// Values/types for special cases.
|
||||
var (
|
||||
identType = reflect.TypeOf((*ast.Ident)(nil))
|
||||
objectPtrType = reflect.TypeOf((*ast.Object)(nil))
|
||||
positionType = reflect.TypeOf(token.NoPos)
|
||||
callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 simplifycompositelit_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/simplifycompositelit"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, simplifycompositelit.Analyzer, "a")
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
type T struct {
|
||||
x, y int
|
||||
}
|
||||
|
||||
type T2 struct {
|
||||
w, z int
|
||||
}
|
||||
|
||||
var _ = [42]T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []struct {
|
||||
x, y int
|
||||
}{
|
||||
struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
T{},
|
||||
10: T{1, 2},
|
||||
20: T{3, 4},
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
([]int{}),
|
||||
([]int{1, 2}),
|
||||
[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][][]int{
|
||||
[][]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
[][]int{ // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
[]int{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]T{
|
||||
"foo": T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": T{},
|
||||
"bar": T{1, 2},
|
||||
"bal": T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": []int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": []int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": []int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": ([]int{}),
|
||||
"bar": ([]int{1, 2}),
|
||||
"bal": []int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
type Piece struct {
|
||||
a int
|
||||
b int
|
||||
c Point
|
||||
d []Point
|
||||
e *Point
|
||||
f *Point
|
||||
}
|
||||
|
||||
// from exp/4s/data.go
|
||||
var pieces3 = []Piece{
|
||||
Piece{0, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
Piece{1, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
Piece{2, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
Piece{3, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [42]*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
&T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: &T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: &T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*struct {
|
||||
x, y int
|
||||
}{
|
||||
&struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: &struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: &struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
&T{},
|
||||
10: &T{1, 2},
|
||||
20: &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
&[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
(&[]int{}),
|
||||
(&[]int{1, 2}),
|
||||
&[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]*[]int{
|
||||
&[]*[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]*[]int{ // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
&[]int{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]*T{
|
||||
"foo": &T{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": &T{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": &T{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": &struct{ x, y int }{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": &struct{ x, y int }{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": &struct{ x, y int }{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": &T{},
|
||||
"bar": &T{1, 2},
|
||||
"bal": &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": &[]int{}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": &[]int{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": &[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": (&[]int{}),
|
||||
"bar": (&[]int{1, 2}),
|
||||
"bal": &[]int{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var pieces4 = []*Piece{
|
||||
&Piece{0, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&Piece{1, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&Piece{2, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&Piece{3, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[T]T2{
|
||||
T{1, 2}: T2{3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
T{5, 6}: T2{7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[*T]*T2{
|
||||
&T{1, 2}: &T2{3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
&T{5, 6}: &T2{7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
type T struct {
|
||||
x, y int
|
||||
}
|
||||
|
||||
type T2 struct {
|
||||
w, z int
|
||||
}
|
||||
|
||||
var _ = [42]T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []struct {
|
||||
x, y int
|
||||
}{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
T{},
|
||||
10: T{1, 2},
|
||||
20: T{3, 4},
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][]int{
|
||||
([]int{}),
|
||||
([]int{1, 2}),
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [][][]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{ // want "redundant type from array, slice, or map composite literal"
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]T{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": T{},
|
||||
"bar": T{1, 2},
|
||||
"bal": T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string][]int{
|
||||
"foo": ([]int{}),
|
||||
"bar": ([]int{1, 2}),
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
type Piece struct {
|
||||
a int
|
||||
b int
|
||||
c Point
|
||||
d []Point
|
||||
e *Point
|
||||
f *Point
|
||||
}
|
||||
|
||||
// from exp/4s/data.go
|
||||
var pieces3 = []Piece{
|
||||
{0, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{1, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{2, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{3, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [42]*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = [...]*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*T{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*struct {
|
||||
x, y int
|
||||
}{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
10: {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
20: {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []interface{}{
|
||||
&T{},
|
||||
10: &T{1, 2},
|
||||
20: &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]int{
|
||||
(&[]int{}),
|
||||
(&[]int{1, 2}),
|
||||
{3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = []*[]*[]int{
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{ // want "redundant type from array, slice, or map composite literal"
|
||||
{}, // want "redundant type from array, slice, or map composite literal"
|
||||
{0, 1, 2, 3}, // want "redundant type from array, slice, or map composite literal"
|
||||
{4, 5}, // want "redundant type from array, slice, or map composite literal"
|
||||
},
|
||||
}
|
||||
|
||||
var _ = map[string]*T{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*struct {
|
||||
x, y int
|
||||
}{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]interface{}{
|
||||
"foo": &T{},
|
||||
"bar": &T{1, 2},
|
||||
"bal": &T{3, 4},
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": {}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bar": {1, 2}, // want "redundant type from array, slice, or map composite literal"
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[string]*[]int{
|
||||
"foo": (&[]int{}),
|
||||
"bar": (&[]int{1, 2}),
|
||||
"bal": {3, 4}, // want "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var pieces4 = []*Piece{
|
||||
{0, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{1, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{2, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{3, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[T]T2{
|
||||
{1, 2}: {3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{5, 6}: {7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
|
||||
var _ = map[*T]*T2{
|
||||
{1, 2}: {3, 4}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
{5, 6}: {7, 8}, // want "redundant type from array, slice, or map composite literal" "redundant type from array, slice, or map composite literal"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
// Package simplifyrange defines an Analyzer that simplifies range statements.
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
//
|
||||
// # Analyzer simplifyrange
|
||||
//
|
||||
// simplifyrange: check for range statement simplifications
|
||||
//
|
||||
// A range of the form:
|
||||
//
|
||||
// for x, _ = range v {...}
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// for x = range v {...}
|
||||
//
|
||||
// A range of the form:
|
||||
//
|
||||
// for _ = range v {...}
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// for range v {...}
|
||||
//
|
||||
// This is one of the simplifications that "gofmt -s" applies.
|
||||
package simplifyrange
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
// Copyright 2020 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 simplifyrange
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "simplifyrange",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "simplifyrange"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange",
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.RangeStmt)(nil),
|
||||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
stmt := n.(*ast.RangeStmt)
|
||||
|
||||
// go1.23's range-over-func requires all vars, blank if necessary.
|
||||
// TODO(adonovan): this may change in go1.24; see #65236.
|
||||
if _, ok := pass.TypesInfo.TypeOf(stmt.X).Underlying().(*types.Signature); ok {
|
||||
return
|
||||
}
|
||||
|
||||
copy := *stmt
|
||||
end := newlineIndex(pass.Fset, ©)
|
||||
|
||||
// Range statements of the form: for i, _ := range x {}
|
||||
var old ast.Expr
|
||||
if isBlank(copy.Value) {
|
||||
old = copy.Value
|
||||
copy.Value = nil
|
||||
}
|
||||
// Range statements of the form: for _ := range x {}
|
||||
if isBlank(copy.Key) && copy.Value == nil {
|
||||
old = copy.Key
|
||||
copy.Key = nil
|
||||
}
|
||||
// Return early if neither if condition is met.
|
||||
if old == nil {
|
||||
return
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: old.Pos(),
|
||||
End: old.End(),
|
||||
Message: "simplify range expression",
|
||||
SuggestedFixes: suggestedFixes(pass.Fset, ©, end),
|
||||
})
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix {
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, fset, rng)
|
||||
stmt := b.Bytes()
|
||||
index := bytes.Index(stmt, []byte("\n"))
|
||||
// If there is a new line character, then don't replace the body.
|
||||
if index != -1 {
|
||||
stmt = stmt[:index]
|
||||
}
|
||||
return []analysis.SuggestedFix{{
|
||||
Message: "Remove empty value",
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: rng.Pos(),
|
||||
End: end,
|
||||
NewText: stmt[:index],
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos {
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, fset, rng)
|
||||
contents := b.Bytes()
|
||||
index := bytes.Index(contents, []byte("\n"))
|
||||
if index == -1 {
|
||||
return rng.End()
|
||||
}
|
||||
return rng.Pos() + token.Pos(index)
|
||||
}
|
||||
|
||||
func isBlank(x ast.Expr) bool {
|
||||
ident, ok := x.(*ast.Ident)
|
||||
return ok && ident.Name == "_"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 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 simplifyrange_test
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/simplifyrange"
|
||||
"golang.org/x/tools/gopls/internal/util/slices"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, simplifyrange.Analyzer, "a")
|
||||
if slices.Contains(build.Default.ReleaseTags, "go1.23") {
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, simplifyrange.Analyzer, "rangeoverfunc")
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
import "log"
|
||||
|
||||
func m() {
|
||||
maps := make(map[string]string)
|
||||
for k, _ := range maps { // want "simplify range expression"
|
||||
log.Println(k)
|
||||
}
|
||||
for _ = range maps { // want "simplify range expression"
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
import "log"
|
||||
|
||||
func m() {
|
||||
maps := make(map[string]string)
|
||||
for k := range maps { // want "simplify range expression"
|
||||
log.Println(k)
|
||||
}
|
||||
for range maps { // want "simplify range expression"
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 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 testdata
|
||||
|
||||
import "iter"
|
||||
|
||||
func _(seq1 iter.Seq[int], seq2 iter.Seq2[int, int]) {
|
||||
for _ = range "" { // want "simplify range expression"
|
||||
}
|
||||
|
||||
// silence
|
||||
for _ = range seq1 {
|
||||
}
|
||||
for _, v := range seq2 {
|
||||
_ = v
|
||||
}
|
||||
for _, _ = range seq2 {
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 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 testdata
|
||||
|
||||
import "iter"
|
||||
|
||||
func _(seq1 iter.Seq[int], seq2 iter.Seq2[int, int]) {
|
||||
for range "" { // want "simplify range expression"
|
||||
}
|
||||
|
||||
// silence
|
||||
for _ = range seq1 {
|
||||
}
|
||||
for _, v := range seq2 {
|
||||
_ = v
|
||||
}
|
||||
for _, _ = range seq2 {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
// Package simplifyslice defines an Analyzer that simplifies slice statements.
|
||||
// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
|
||||
// https://golang.org/cmd/gofmt/#hdr-The_simplify_command
|
||||
//
|
||||
// # Analyzer simplifyslice
|
||||
//
|
||||
// simplifyslice: check for slice simplifications
|
||||
//
|
||||
// A slice expression of the form:
|
||||
//
|
||||
// s[a:len(s)]
|
||||
//
|
||||
// will be simplified to:
|
||||
//
|
||||
// s[a:]
|
||||
//
|
||||
// This is one of the simplifications that "gofmt -s" applies.
|
||||
package simplifyslice
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
|
||||
package simplifyslice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "simplifyslice",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "simplifyslice"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice",
|
||||
}
|
||||
|
||||
// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
|
||||
// but we leave them as is since sometimes we want to be very explicit
|
||||
// about the lower bound.
|
||||
// An example where the 0 helps:
|
||||
// x, y, z := b[0:2], b[2:4], b[4:6]
|
||||
// An example where it does not:
|
||||
// x, y := b[:n], b[n:]
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.SliceExpr)(nil),
|
||||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
expr := n.(*ast.SliceExpr)
|
||||
// - 3-index slices always require the 2nd and 3rd index
|
||||
if expr.Max != nil {
|
||||
return
|
||||
}
|
||||
s, ok := expr.X.(*ast.Ident)
|
||||
// the array/slice object is a single, resolved identifier
|
||||
if !ok || s.Obj == nil {
|
||||
return
|
||||
}
|
||||
call, ok := expr.High.(*ast.CallExpr)
|
||||
// the high expression is a function call with a single argument
|
||||
if !ok || len(call.Args) != 1 || call.Ellipsis.IsValid() {
|
||||
return
|
||||
}
|
||||
fun, ok := call.Fun.(*ast.Ident)
|
||||
// the function called is "len" and it is not locally defined; and
|
||||
// because we don't have dot imports, it must be the predefined len()
|
||||
if !ok || fun.Name != "len" || fun.Obj != nil {
|
||||
return
|
||||
}
|
||||
arg, ok := call.Args[0].(*ast.Ident)
|
||||
// the len argument is the array/slice object
|
||||
if !ok || arg.Obj != s.Obj {
|
||||
return
|
||||
}
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, pass.Fset, expr.High)
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: expr.High.Pos(),
|
||||
End: expr.High.End(),
|
||||
Message: fmt.Sprintf("unneeded: %s", b.String()),
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Remove '%s'", b.String()),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: expr.High.Pos(),
|
||||
End: expr.High.End(),
|
||||
NewText: []byte{},
|
||||
}},
|
||||
}},
|
||||
})
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 simplifyslice_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/simplifyslice"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, simplifyslice.Analyzer, "a", "typeparams")
|
||||
}
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
var (
|
||||
a [10]byte
|
||||
b [20]float32
|
||||
s []int
|
||||
t struct {
|
||||
s []byte
|
||||
}
|
||||
|
||||
_ = a[0:]
|
||||
_ = a[1:10]
|
||||
_ = a[2:len(a)] // want "unneeded: len\\(a\\)"
|
||||
_ = a[3:(len(a))]
|
||||
_ = a[len(a)-1 : len(a)] // want "unneeded: len\\(a\\)"
|
||||
_ = a[2:len(a):len(a)]
|
||||
|
||||
_ = a[:]
|
||||
_ = a[:10]
|
||||
_ = a[:len(a)] // want "unneeded: len\\(a\\)"
|
||||
_ = a[:(len(a))]
|
||||
_ = a[:len(a)-1]
|
||||
_ = a[:len(a):len(a)]
|
||||
|
||||
_ = s[0:]
|
||||
_ = s[1:10]
|
||||
_ = s[2:len(s)] // want "unneeded: len\\(s\\)"
|
||||
_ = s[3:(len(s))]
|
||||
_ = s[len(a) : len(s)-1]
|
||||
_ = s[0:len(b)]
|
||||
_ = s[2:len(s):len(s)]
|
||||
|
||||
_ = s[:]
|
||||
_ = s[:10]
|
||||
_ = s[:len(s)] // want "unneeded: len\\(s\\)"
|
||||
_ = s[:(len(s))]
|
||||
_ = s[:len(s)-1]
|
||||
_ = s[:len(b)]
|
||||
_ = s[:len(s):len(s)]
|
||||
|
||||
_ = t.s[0:]
|
||||
_ = t.s[1:10]
|
||||
_ = t.s[2:len(t.s)]
|
||||
_ = t.s[3:(len(t.s))]
|
||||
_ = t.s[len(a) : len(t.s)-1]
|
||||
_ = t.s[0:len(b)]
|
||||
_ = t.s[2:len(t.s):len(t.s)]
|
||||
|
||||
_ = t.s[:]
|
||||
_ = t.s[:10]
|
||||
_ = t.s[:len(t.s)]
|
||||
_ = t.s[:(len(t.s))]
|
||||
_ = t.s[:len(t.s)-1]
|
||||
_ = t.s[:len(b)]
|
||||
_ = t.s[:len(t.s):len(t.s)]
|
||||
)
|
||||
|
||||
func _() {
|
||||
s := s[0:len(s)] // want "unneeded: len\\(s\\)"
|
||||
_ = s
|
||||
}
|
||||
|
||||
func m() {
|
||||
maps := []int{}
|
||||
_ = maps[1:len(maps)] // want "unneeded: len\\(maps\\)"
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2020 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 testdata
|
||||
|
||||
var (
|
||||
a [10]byte
|
||||
b [20]float32
|
||||
s []int
|
||||
t struct {
|
||||
s []byte
|
||||
}
|
||||
|
||||
_ = a[0:]
|
||||
_ = a[1:10]
|
||||
_ = a[2:] // want "unneeded: len\\(a\\)"
|
||||
_ = a[3:(len(a))]
|
||||
_ = a[len(a)-1:] // want "unneeded: len\\(a\\)"
|
||||
_ = a[2:len(a):len(a)]
|
||||
|
||||
_ = a[:]
|
||||
_ = a[:10]
|
||||
_ = a[:] // want "unneeded: len\\(a\\)"
|
||||
_ = a[:(len(a))]
|
||||
_ = a[:len(a)-1]
|
||||
_ = a[:len(a):len(a)]
|
||||
|
||||
_ = s[0:]
|
||||
_ = s[1:10]
|
||||
_ = s[2:] // want "unneeded: len\\(s\\)"
|
||||
_ = s[3:(len(s))]
|
||||
_ = s[len(a) : len(s)-1]
|
||||
_ = s[0:len(b)]
|
||||
_ = s[2:len(s):len(s)]
|
||||
|
||||
_ = s[:]
|
||||
_ = s[:10]
|
||||
_ = s[:] // want "unneeded: len\\(s\\)"
|
||||
_ = s[:(len(s))]
|
||||
_ = s[:len(s)-1]
|
||||
_ = s[:len(b)]
|
||||
_ = s[:len(s):len(s)]
|
||||
|
||||
_ = t.s[0:]
|
||||
_ = t.s[1:10]
|
||||
_ = t.s[2:len(t.s)]
|
||||
_ = t.s[3:(len(t.s))]
|
||||
_ = t.s[len(a) : len(t.s)-1]
|
||||
_ = t.s[0:len(b)]
|
||||
_ = t.s[2:len(t.s):len(t.s)]
|
||||
|
||||
_ = t.s[:]
|
||||
_ = t.s[:10]
|
||||
_ = t.s[:len(t.s)]
|
||||
_ = t.s[:(len(t.s))]
|
||||
_ = t.s[:len(t.s)-1]
|
||||
_ = t.s[:len(b)]
|
||||
_ = t.s[:len(t.s):len(t.s)]
|
||||
)
|
||||
|
||||
func _() {
|
||||
s := s[0:] // want "unneeded: len\\(s\\)"
|
||||
_ = s
|
||||
}
|
||||
|
||||
func m() {
|
||||
maps := []int{}
|
||||
_ = maps[1:] // want "unneeded: len\\(maps\\)"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2021 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 testdata
|
||||
|
||||
type List[E any] []E
|
||||
|
||||
// TODO(suzmue): add a test for generic slice expressions when https://github.com/golang/go/issues/48618 is closed.
|
||||
// type S interface{ ~[]int }
|
||||
|
||||
var (
|
||||
a [10]byte
|
||||
b [20]float32
|
||||
p List[int]
|
||||
|
||||
_ = p[0:]
|
||||
_ = p[1:10]
|
||||
_ = p[2:len(p)] // want "unneeded: len\\(p\\)"
|
||||
_ = p[3:(len(p))]
|
||||
_ = p[len(a) : len(p)-1]
|
||||
_ = p[0:len(b)]
|
||||
_ = p[2:len(p):len(p)]
|
||||
|
||||
_ = p[:]
|
||||
_ = p[:10]
|
||||
_ = p[:len(p)] // want "unneeded: len\\(p\\)"
|
||||
_ = p[:(len(p))]
|
||||
_ = p[:len(p)-1]
|
||||
_ = p[:len(b)]
|
||||
_ = p[:len(p):len(p)]
|
||||
)
|
||||
|
||||
func foo[E any](a List[E]) {
|
||||
_ = a[0:len(a)] // want "unneeded: len\\(a\\)"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2021 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 testdata
|
||||
|
||||
type List[E any] []E
|
||||
|
||||
// TODO(suzmue): add a test for generic slice expressions when https://github.com/golang/go/issues/48618 is closed.
|
||||
// type S interface{ ~[]int }
|
||||
|
||||
var (
|
||||
a [10]byte
|
||||
b [20]float32
|
||||
p List[int]
|
||||
|
||||
_ = p[0:]
|
||||
_ = p[1:10]
|
||||
_ = p[2:] // want "unneeded: len\\(p\\)"
|
||||
_ = p[3:(len(p))]
|
||||
_ = p[len(a) : len(p)-1]
|
||||
_ = p[0:len(b)]
|
||||
_ = p[2:len(p):len(p)]
|
||||
|
||||
_ = p[:]
|
||||
_ = p[:10]
|
||||
_ = p[:] // want "unneeded: len\\(p\\)"
|
||||
_ = p[:(len(p))]
|
||||
_ = p[:len(p)-1]
|
||||
_ = p[:len(b)]
|
||||
_ = p[:len(p):len(p)]
|
||||
)
|
||||
|
||||
func foo[E any](a List[E]) {
|
||||
_ = a[0:] // want "unneeded: len\\(a\\)"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
// Package stubmethods defines a code action for missing interface methods.
|
||||
//
|
||||
// # Analyzer stubmethods
|
||||
//
|
||||
// stubmethods: detect missing methods and fix with stub implementations
|
||||
//
|
||||
// This analyzer detects type-checking errors due to missing methods
|
||||
// in assignments from concrete types to interface types, and offers
|
||||
// a suggested fix that will create a set of stub methods so that
|
||||
// the concrete type satisfies the interface.
|
||||
//
|
||||
// For example, this function will not compile because the value
|
||||
// NegativeErr{} does not implement the "error" interface:
|
||||
//
|
||||
// func sqrt(x float64) (float64, error) {
|
||||
// if x < 0 {
|
||||
// return 0, NegativeErr{} // error: missing method
|
||||
// }
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// type NegativeErr struct{}
|
||||
//
|
||||
// This analyzer will suggest a fix to declare this method:
|
||||
//
|
||||
// // Error implements error.Error.
|
||||
// func (NegativeErr) Error() string {
|
||||
// panic("unimplemented")
|
||||
// }
|
||||
//
|
||||
// (At least, it appears to behave that way, but technically it
|
||||
// doesn't use the SuggestedFix mechanism and the stub is created by
|
||||
// logic in gopls's golang.stub function.)
|
||||
package stubmethods
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package stubmethods
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/gopls/internal/util/typesutil"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "stubmethods",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "stubmethods"),
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods",
|
||||
}
|
||||
|
||||
// TODO(rfindley): remove this thin wrapper around the stubmethods refactoring,
|
||||
// and eliminate the stubmethods analyzer.
|
||||
//
|
||||
// Previous iterations used the analysis framework for computing refactorings,
|
||||
// which proved inefficient.
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, err := range pass.TypeErrors {
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= err.Pos && err.Pos < f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
// Get the end position of the error.
|
||||
_, _, end, ok := typesinternal.ReadGo116ErrorData(err)
|
||||
if !ok {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, pass.Fset, file); err != nil {
|
||||
continue
|
||||
}
|
||||
end = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
|
||||
}
|
||||
if diag, ok := DiagnosticForError(pass.Fset, file, err.Pos, end, err.Msg, pass.TypesInfo); ok {
|
||||
pass.Report(diag)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MatchesMessage reports whether msg matches the error message sought after by
|
||||
// the stubmethods fix.
|
||||
func MatchesMessage(msg string) bool {
|
||||
return strings.Contains(msg, "missing method") || strings.HasPrefix(msg, "cannot convert") || strings.Contains(msg, "not implement")
|
||||
}
|
||||
|
||||
// DiagnosticForError computes a diagnostic suggesting to implement an
|
||||
// interface to fix the type checking error defined by (start, end, msg).
|
||||
//
|
||||
// If no such fix is possible, the second result is false.
|
||||
func DiagnosticForError(fset *token.FileSet, file *ast.File, start, end token.Pos, msg string, info *types.Info) (analysis.Diagnostic, bool) {
|
||||
if !MatchesMessage(msg) {
|
||||
return analysis.Diagnostic{}, false
|
||||
}
|
||||
|
||||
path, _ := astutil.PathEnclosingInterval(file, start, end)
|
||||
si := GetStubInfo(fset, info, path, start)
|
||||
if si == nil {
|
||||
return analysis.Diagnostic{}, false
|
||||
}
|
||||
qf := typesutil.FileQualifier(file, si.Concrete.Obj().Pkg(), info)
|
||||
iface := types.TypeString(si.Interface.Type(), qf)
|
||||
return analysis.Diagnostic{
|
||||
Pos: start,
|
||||
End: end,
|
||||
Message: msg,
|
||||
Category: FixCategory,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Declare missing methods of %s", iface),
|
||||
// No TextEdits => computed later by gopls.
|
||||
}},
|
||||
}, true
|
||||
}
|
||||
|
||||
const FixCategory = "stubmethods" // recognized by gopls ApplyFix
|
||||
|
||||
// StubInfo represents a concrete type
|
||||
// that wants to stub out an interface type
|
||||
type StubInfo struct {
|
||||
// Interface is the interface that the client wants to implement.
|
||||
// When the interface is defined, the underlying object will be a TypeName.
|
||||
// Note that we keep track of types.Object instead of types.Type in order
|
||||
// to keep a reference to the declaring object's package and the ast file
|
||||
// in the case where the concrete type file requires a new import that happens to be renamed
|
||||
// in the interface file.
|
||||
// TODO(marwan-at-work): implement interface literals.
|
||||
Fset *token.FileSet // the FileSet used to type-check the types below
|
||||
Interface *types.TypeName
|
||||
Concrete *types.Named
|
||||
Pointer bool
|
||||
}
|
||||
|
||||
// GetStubInfo determines whether the "missing method error"
|
||||
// can be used to deduced what the concrete and interface types are.
|
||||
//
|
||||
// TODO(adonovan): this function (and its following 5 helpers) tries
|
||||
// to deduce a pair of (concrete, interface) types that are related by
|
||||
// an assignment, either explicitly or through a return statement or
|
||||
// function call. This is essentially what the refactor/satisfy does,
|
||||
// more generally. Refactor to share logic, after auditing 'satisfy'
|
||||
// for safety on ill-typed code.
|
||||
func GetStubInfo(fset *token.FileSet, info *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
|
||||
for _, n := range path {
|
||||
switch n := n.(type) {
|
||||
case *ast.ValueSpec:
|
||||
return fromValueSpec(fset, info, n, pos)
|
||||
case *ast.ReturnStmt:
|
||||
// An error here may not indicate a real error the user should know about, but it may.
|
||||
// Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
|
||||
// it. However, event.Log takes a context which is not passed via the analysis package.
|
||||
// TODO(marwan-at-work): properly log this error.
|
||||
si, _ := fromReturnStmt(fset, info, pos, path, n)
|
||||
return si
|
||||
case *ast.AssignStmt:
|
||||
return fromAssignStmt(fset, info, n, pos)
|
||||
case *ast.CallExpr:
|
||||
// Note that some call expressions don't carry the interface type
|
||||
// because they don't point to a function or method declaration elsewhere.
|
||||
// For eaxmple, "var Interface = (*Concrete)(nil)". In that case, continue
|
||||
// this loop to encounter other possibilities such as *ast.ValueSpec or others.
|
||||
si := fromCallExpr(fset, info, pos, n)
|
||||
if si != nil {
|
||||
return si
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fromCallExpr tries to find an *ast.CallExpr's function declaration and
|
||||
// analyzes a function call's signature against the passed in parameter to deduce
|
||||
// the concrete and interface types.
|
||||
func fromCallExpr(fset *token.FileSet, info *types.Info, pos token.Pos, call *ast.CallExpr) *StubInfo {
|
||||
// Find argument containing pos.
|
||||
argIdx := -1
|
||||
var arg ast.Expr
|
||||
for i, callArg := range call.Args {
|
||||
if callArg.Pos() <= pos && pos <= callArg.End() {
|
||||
argIdx = i
|
||||
arg = callArg
|
||||
break
|
||||
}
|
||||
}
|
||||
if arg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
concType, pointer := concreteType(arg, info)
|
||||
if concType == nil || concType.Obj().Pkg() == nil {
|
||||
return nil
|
||||
}
|
||||
tv, ok := info.Types[call.Fun]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
sig, ok := aliases.Unalias(tv.Type).(*types.Signature)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var paramType types.Type
|
||||
if sig.Variadic() && argIdx >= sig.Params().Len()-1 {
|
||||
v := sig.Params().At(sig.Params().Len() - 1)
|
||||
if s, _ := v.Type().(*types.Slice); s != nil {
|
||||
paramType = s.Elem()
|
||||
}
|
||||
} else if argIdx < sig.Params().Len() {
|
||||
paramType = sig.Params().At(argIdx).Type()
|
||||
}
|
||||
if paramType == nil {
|
||||
return nil // A type error prevents us from determining the param type.
|
||||
}
|
||||
iface := ifaceObjFromType(paramType)
|
||||
if iface == nil {
|
||||
return nil
|
||||
}
|
||||
return &StubInfo{
|
||||
Fset: fset,
|
||||
Concrete: concType,
|
||||
Pointer: pointer,
|
||||
Interface: iface,
|
||||
}
|
||||
}
|
||||
|
||||
// fromReturnStmt analyzes a "return" statement to extract
|
||||
// a concrete type that is trying to be returned as an interface type.
|
||||
//
|
||||
// For example, func() io.Writer { return myType{} }
|
||||
// would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
|
||||
func fromReturnStmt(fset *token.FileSet, info *types.Info, pos token.Pos, path []ast.Node, ret *ast.ReturnStmt) (*StubInfo, error) {
|
||||
// Find return operand containing pos.
|
||||
returnIdx := -1
|
||||
for i, r := range ret.Results {
|
||||
if r.Pos() <= pos && pos <= r.End() {
|
||||
returnIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if returnIdx == -1 {
|
||||
return nil, fmt.Errorf("pos %d not within return statement bounds: [%d-%d]", pos, ret.Pos(), ret.End())
|
||||
}
|
||||
|
||||
concType, pointer := concreteType(ret.Results[returnIdx], info)
|
||||
if concType == nil || concType.Obj().Pkg() == nil {
|
||||
return nil, nil
|
||||
}
|
||||
funcType := enclosingFunction(path, info)
|
||||
if funcType == nil {
|
||||
return nil, fmt.Errorf("could not find the enclosing function of the return statement")
|
||||
}
|
||||
if len(funcType.Results.List) != len(ret.Results) {
|
||||
return nil, fmt.Errorf("%d-operand return statement in %d-result function",
|
||||
len(ret.Results),
|
||||
len(funcType.Results.List))
|
||||
}
|
||||
iface := ifaceType(funcType.Results.List[returnIdx].Type, info)
|
||||
if iface == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &StubInfo{
|
||||
Fset: fset,
|
||||
Concrete: concType,
|
||||
Pointer: pointer,
|
||||
Interface: iface,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fromValueSpec returns *StubInfo from a variable declaration such as
|
||||
// var x io.Writer = &T{}
|
||||
func fromValueSpec(fset *token.FileSet, info *types.Info, spec *ast.ValueSpec, pos token.Pos) *StubInfo {
|
||||
// Find RHS element containing pos.
|
||||
var rhs ast.Expr
|
||||
for _, r := range spec.Values {
|
||||
if r.Pos() <= pos && pos <= r.End() {
|
||||
rhs = r
|
||||
break
|
||||
}
|
||||
}
|
||||
if rhs == nil {
|
||||
return nil // e.g. pos was on the LHS (#64545)
|
||||
}
|
||||
|
||||
// Possible implicit/explicit conversion to interface type?
|
||||
ifaceNode := spec.Type // var _ myInterface = ...
|
||||
if call, ok := rhs.(*ast.CallExpr); ok && ifaceNode == nil && len(call.Args) == 1 {
|
||||
// var _ = myInterface(v)
|
||||
ifaceNode = call.Fun
|
||||
rhs = call.Args[0]
|
||||
}
|
||||
concType, pointer := concreteType(rhs, info)
|
||||
if concType == nil || concType.Obj().Pkg() == nil {
|
||||
return nil
|
||||
}
|
||||
ifaceObj := ifaceType(ifaceNode, info)
|
||||
if ifaceObj == nil {
|
||||
return nil
|
||||
}
|
||||
return &StubInfo{
|
||||
Fset: fset,
|
||||
Concrete: concType,
|
||||
Interface: ifaceObj,
|
||||
Pointer: pointer,
|
||||
}
|
||||
}
|
||||
|
||||
// fromAssignStmt returns *StubInfo from a variable assignment such as
|
||||
// var x io.Writer
|
||||
// x = &T{}
|
||||
func fromAssignStmt(fset *token.FileSet, info *types.Info, assign *ast.AssignStmt, pos token.Pos) *StubInfo {
|
||||
// The interface conversion error in an assignment is against the RHS:
|
||||
//
|
||||
// var x io.Writer
|
||||
// x = &T{} // error: missing method
|
||||
// ^^^^
|
||||
//
|
||||
// Find RHS element containing pos.
|
||||
var lhs, rhs ast.Expr
|
||||
for i, r := range assign.Rhs {
|
||||
if r.Pos() <= pos && pos <= r.End() {
|
||||
if i >= len(assign.Lhs) {
|
||||
// This should never happen as we would get a
|
||||
// "cannot assign N values to M variables"
|
||||
// before we get an interface conversion error.
|
||||
// But be defensive.
|
||||
return nil
|
||||
}
|
||||
lhs = assign.Lhs[i]
|
||||
rhs = r
|
||||
break
|
||||
}
|
||||
}
|
||||
if lhs == nil || rhs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ifaceObj := ifaceType(lhs, info)
|
||||
if ifaceObj == nil {
|
||||
return nil
|
||||
}
|
||||
concType, pointer := concreteType(rhs, info)
|
||||
if concType == nil || concType.Obj().Pkg() == nil {
|
||||
return nil
|
||||
}
|
||||
return &StubInfo{
|
||||
Fset: fset,
|
||||
Concrete: concType,
|
||||
Interface: ifaceObj,
|
||||
Pointer: pointer,
|
||||
}
|
||||
}
|
||||
|
||||
// ifaceType returns the named interface type to which e refers, if any.
|
||||
func ifaceType(e ast.Expr, info *types.Info) *types.TypeName {
|
||||
tv, ok := info.Types[e]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return ifaceObjFromType(tv.Type)
|
||||
}
|
||||
|
||||
func ifaceObjFromType(t types.Type) *types.TypeName {
|
||||
named, ok := aliases.Unalias(t).(*types.Named)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if !types.IsInterface(named) {
|
||||
return nil
|
||||
}
|
||||
// Interfaces defined in the "builtin" package return nil a Pkg().
|
||||
// But they are still real interfaces that we need to make a special case for.
|
||||
// Therefore, protect gopls from panicking if a new interface type was added in the future.
|
||||
if named.Obj().Pkg() == nil && named.Obj().Name() != "error" {
|
||||
return nil
|
||||
}
|
||||
return named.Obj()
|
||||
}
|
||||
|
||||
// concreteType tries to extract the *types.Named that defines
|
||||
// the concrete type given the ast.Expr where the "missing method"
|
||||
// or "conversion" errors happened. If the concrete type is something
|
||||
// that cannot have methods defined on it (such as basic types), this
|
||||
// method will return a nil *types.Named. The second return parameter
|
||||
// is a boolean that indicates whether the concreteType was defined as a
|
||||
// pointer or value.
|
||||
func concreteType(e ast.Expr, info *types.Info) (*types.Named, bool) {
|
||||
tv, ok := info.Types[e]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
typ := tv.Type
|
||||
ptr, isPtr := aliases.Unalias(typ).(*types.Pointer)
|
||||
if isPtr {
|
||||
typ = ptr.Elem()
|
||||
}
|
||||
named, ok := aliases.Unalias(typ).(*types.Named)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return named, isPtr
|
||||
}
|
||||
|
||||
// enclosingFunction returns the signature and type of the function
|
||||
// enclosing the given position.
|
||||
func enclosingFunction(path []ast.Node, info *types.Info) *ast.FuncType {
|
||||
for _, node := range path {
|
||||
switch t := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if _, ok := info.Defs[t.Name]; ok {
|
||||
return t.Type
|
||||
}
|
||||
case *ast.FuncLit:
|
||||
if _, ok := info.Types[t]; ok {
|
||||
return t.Type
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
package stubmethods_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/stubmethods"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, stubmethods.Analyzer, "typeparams")
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
package stubmethods
|
||||
|
||||
var _ I = Y{} // want "does not implement I"
|
||||
|
||||
type I interface{ F() }
|
||||
|
||||
type X struct{}
|
||||
|
||||
func (X) F(string) {}
|
||||
|
||||
type Y struct{ X }
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
// Package undeclaredname defines an Analyzer that applies suggested fixes
|
||||
// to errors of the type "undeclared name: %s".
|
||||
//
|
||||
// # Analyzer undeclaredname
|
||||
//
|
||||
// undeclaredname: suggested fixes for "undeclared name: <>"
|
||||
//
|
||||
// This checker provides suggested fixes for type errors of the
|
||||
// type "undeclared name: <>". It will either insert a new statement,
|
||||
// such as:
|
||||
//
|
||||
// <> :=
|
||||
//
|
||||
// or a new function declaration, such as:
|
||||
//
|
||||
// func <>(inferred parameters) {
|
||||
// panic("implement me!")
|
||||
// }
|
||||
package undeclaredname
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 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 undeclared
|
||||
|
||||
func x() int {
|
||||
var z int
|
||||
z = y // want "(undeclared name|undefined): y"
|
||||
|
||||
if z == m { // want "(undeclared name|undefined): m"
|
||||
z = 1
|
||||
}
|
||||
|
||||
if z == 1 {
|
||||
z = 1
|
||||
} else if z == n+1 { // want "(undeclared name|undefined): n"
|
||||
z = 1
|
||||
}
|
||||
|
||||
switch z {
|
||||
case 10:
|
||||
z = 1
|
||||
case a: // want "(undeclared name|undefined): a"
|
||||
z = 1
|
||||
}
|
||||
return z
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func channels(s string) {
|
||||
undefinedChannels(c()) // want "(undeclared name|undefined): undefinedChannels"
|
||||
}
|
||||
|
||||
func c() (<-chan string, chan string) {
|
||||
return make(<-chan string), make(chan string)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func consecutiveParams() {
|
||||
var s string
|
||||
undefinedConsecutiveParams(s, s) // want "(undeclared name|undefined): undefinedConsecutiveParams"
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func errorParam() {
|
||||
var err error
|
||||
undefinedErrorParam(err) // want "(undeclared name|undefined): undefinedErrorParam"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
type T struct{}
|
||||
|
||||
func literals() {
|
||||
undefinedLiterals("hey compiler", T{}, &T{}) // want "(undeclared name|undefined): undefinedLiterals"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
import "time"
|
||||
|
||||
func operation() {
|
||||
undefinedOperation(10 * time.Second) // want "(undeclared name|undefined): undefinedOperation"
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func selector() {
|
||||
m := map[int]bool{}
|
||||
undefinedSelector(m[1]) // want "(undeclared name|undefined): undefinedSelector"
|
||||
}
|
||||
go/pkg/mod/golang.org/x/tools/gopls@v0.16.2/internal/analysis/undeclaredname/testdata/src/a/slice.go
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func slice() {
|
||||
undefinedSlice([]int{1, 2}) // want "(undeclared name|undefined): undefinedSlice"
|
||||
}
|
||||
go/pkg/mod/golang.org/x/tools/gopls@v0.16.2/internal/analysis/undeclaredname/testdata/src/a/tuple.go
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func tuple() {
|
||||
undefinedTuple(b()) // want "(undeclared name|undefined): undefinedTuple"
|
||||
}
|
||||
|
||||
func b() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 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 undeclared
|
||||
|
||||
func uniqueArguments() {
|
||||
var s string
|
||||
var i int
|
||||
undefinedUniqueArguments(s, i, s) // want "(undeclared name|undefined): undefinedUniqueArguments"
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
// Copyright 2020 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 undeclaredname
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/gopls/internal/util/safetoken"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "undeclaredname",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "undeclaredname"),
|
||||
Requires: []*analysis.Analyzer{},
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname",
|
||||
}
|
||||
|
||||
// The prefix for this error message changed in Go 1.20.
|
||||
var undeclaredNamePrefixes = []string{"undeclared name: ", "undefined: "}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, err := range pass.TypeErrors {
|
||||
runForError(pass, err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func runForError(pass *analysis.Pass, err types.Error) {
|
||||
// Extract symbol name from error.
|
||||
var name string
|
||||
for _, prefix := range undeclaredNamePrefixes {
|
||||
if !strings.HasPrefix(err.Msg, prefix) {
|
||||
continue
|
||||
}
|
||||
name = strings.TrimPrefix(err.Msg, prefix)
|
||||
}
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Find file enclosing error.
|
||||
var file *ast.File
|
||||
for _, f := range pass.Files {
|
||||
if f.Pos() <= err.Pos && err.Pos < f.End() {
|
||||
file = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Find path to identifier in the error.
|
||||
path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos)
|
||||
if len(path) < 2 {
|
||||
return
|
||||
}
|
||||
ident, ok := path[0].(*ast.Ident)
|
||||
if !ok || ident.Name != name {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip selector expressions because it might be too complex
|
||||
// to try and provide a suggested fix for fields and methods.
|
||||
if _, ok := path[1].(*ast.SelectorExpr); ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Undeclared quick fixes only work in function bodies.
|
||||
inFunc := false
|
||||
for i := range path {
|
||||
if _, inFunc = path[i].(*ast.FuncDecl); inFunc {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
if _, isBody := path[i-1].(*ast.BlockStmt); !isBody {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// Offer a fix.
|
||||
noun := "variable"
|
||||
if isCallPosition(path) {
|
||||
noun = "function"
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: err.Pos,
|
||||
End: err.Pos + token.Pos(len(name)),
|
||||
Message: err.Msg,
|
||||
Category: FixCategory,
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Create %s %q", noun, name),
|
||||
// No TextEdits => computed by a gopls command
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
const FixCategory = "undeclaredname" // recognized by gopls ApplyFix
|
||||
|
||||
// SuggestedFix computes the edits for the lazy (no-edits) fix suggested by the analyzer.
|
||||
func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
|
||||
pos := start // don't use the end
|
||||
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
||||
if len(path) < 2 {
|
||||
return nil, nil, fmt.Errorf("no expression found")
|
||||
}
|
||||
ident, ok := path[0].(*ast.Ident)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("no identifier found")
|
||||
}
|
||||
|
||||
// Check for a possible call expression, in which case we should add a
|
||||
// new function declaration.
|
||||
if isCallPosition(path) {
|
||||
return newFunctionDeclaration(path, file, pkg, info, fset)
|
||||
}
|
||||
|
||||
// Get the place to insert the new statement.
|
||||
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
|
||||
if insertBeforeStmt == nil {
|
||||
return nil, nil, fmt.Errorf("could not locate insertion point")
|
||||
}
|
||||
|
||||
insertBefore := safetoken.StartPosition(fset, insertBeforeStmt.Pos()).Offset
|
||||
|
||||
// Get the indent to add on the line after the new statement.
|
||||
// Since this will have a parse error, we can not use format.Source().
|
||||
contentBeforeStmt, indent := content[:insertBefore], "\n"
|
||||
if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
|
||||
indent = string(contentBeforeStmt[nl:])
|
||||
}
|
||||
|
||||
// Create the new local variable statement.
|
||||
newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
|
||||
return fset, &analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("Create variable %q", ident.Name),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: insertBeforeStmt.Pos(),
|
||||
End: insertBeforeStmt.Pos(),
|
||||
NewText: []byte(newStmt),
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*token.FileSet, *analysis.SuggestedFix, error) {
|
||||
if len(path) < 3 {
|
||||
return nil, nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)
|
||||
}
|
||||
ident, ok := path[0].(*ast.Ident)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0])
|
||||
}
|
||||
call, ok := path[1].(*ast.CallExpr)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("no call expression found %v (%T)", path[1], path[1])
|
||||
}
|
||||
|
||||
// Find the enclosing function, so that we can add the new declaration
|
||||
// below.
|
||||
var enclosing *ast.FuncDecl
|
||||
for _, n := range path {
|
||||
if n, ok := n.(*ast.FuncDecl); ok {
|
||||
enclosing = n
|
||||
break
|
||||
}
|
||||
}
|
||||
// TODO(rstambler): Support the situation when there is no enclosing
|
||||
// function.
|
||||
if enclosing == nil {
|
||||
return nil, nil, fmt.Errorf("no enclosing function found: %v", path)
|
||||
}
|
||||
|
||||
pos := enclosing.End()
|
||||
|
||||
var paramNames []string
|
||||
var paramTypes []types.Type
|
||||
// keep track of all param names to later ensure uniqueness
|
||||
nameCounts := map[string]int{}
|
||||
for _, arg := range call.Args {
|
||||
typ := info.TypeOf(arg)
|
||||
if typ == nil {
|
||||
return nil, nil, fmt.Errorf("unable to determine type for %s", arg)
|
||||
}
|
||||
|
||||
switch t := typ.(type) {
|
||||
// this is the case where another function call returning multiple
|
||||
// results is used as an argument
|
||||
case *types.Tuple:
|
||||
n := t.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
name := typeToArgName(t.At(i).Type())
|
||||
nameCounts[name]++
|
||||
|
||||
paramNames = append(paramNames, name)
|
||||
paramTypes = append(paramTypes, types.Default(t.At(i).Type()))
|
||||
}
|
||||
|
||||
default:
|
||||
// does the argument have a name we can reuse?
|
||||
// only happens in case of a *ast.Ident
|
||||
var name string
|
||||
if ident, ok := arg.(*ast.Ident); ok {
|
||||
name = ident.Name
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = typeToArgName(typ)
|
||||
}
|
||||
|
||||
nameCounts[name]++
|
||||
|
||||
paramNames = append(paramNames, name)
|
||||
paramTypes = append(paramTypes, types.Default(typ))
|
||||
}
|
||||
}
|
||||
|
||||
for n, c := range nameCounts {
|
||||
// Any names we saw more than once will need a unique suffix added
|
||||
// on. Reset the count to 1 to act as the suffix for the first
|
||||
// occurrence of that name.
|
||||
if c >= 2 {
|
||||
nameCounts[n] = 1
|
||||
} else {
|
||||
delete(nameCounts, n)
|
||||
}
|
||||
}
|
||||
|
||||
params := &ast.FieldList{}
|
||||
|
||||
for i, name := range paramNames {
|
||||
if suffix, repeats := nameCounts[name]; repeats {
|
||||
nameCounts[name]++
|
||||
name = fmt.Sprintf("%s%d", name, suffix)
|
||||
}
|
||||
|
||||
// only worth checking after previous param in the list
|
||||
if i > 0 {
|
||||
// if type of parameter at hand is the same as the previous one,
|
||||
// add it to the previous param list of identifiers so to have:
|
||||
// (s1, s2 string)
|
||||
// and not
|
||||
// (s1 string, s2 string)
|
||||
if paramTypes[i] == paramTypes[i-1] {
|
||||
params.List[len(params.List)-1].Names = append(params.List[len(params.List)-1].Names, ast.NewIdent(name))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
params.List = append(params.List, &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
ast.NewIdent(name),
|
||||
},
|
||||
Type: analysisinternal.TypeExpr(file, pkg, paramTypes[i]),
|
||||
})
|
||||
}
|
||||
|
||||
decl := &ast.FuncDecl{
|
||||
Name: ast.NewIdent(ident.Name),
|
||||
Type: &ast.FuncType{
|
||||
Params: params,
|
||||
// TODO(golang/go#47558): Also handle result
|
||||
// parameters here based on context of CallExpr.
|
||||
},
|
||||
Body: &ast.BlockStmt{
|
||||
List: []ast.Stmt{
|
||||
&ast.ExprStmt{
|
||||
X: &ast.CallExpr{
|
||||
Fun: ast.NewIdent("panic"),
|
||||
Args: []ast.Expr{
|
||||
&ast.BasicLit{
|
||||
Value: `"unimplemented"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b := bytes.NewBufferString("\n\n")
|
||||
if err := format.Node(b, fset, decl); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return fset, &analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("Create function %q", ident.Name),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: pos,
|
||||
End: pos,
|
||||
NewText: b.Bytes(),
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func typeToArgName(ty types.Type) string {
|
||||
s := types.Default(ty).String()
|
||||
|
||||
switch t := aliases.Unalias(ty).(type) {
|
||||
case *types.Basic:
|
||||
// use first letter in type name for basic types
|
||||
return s[0:1]
|
||||
case *types.Slice:
|
||||
// use element type to decide var name for slices
|
||||
return typeToArgName(t.Elem())
|
||||
case *types.Array:
|
||||
// use element type to decide var name for arrays
|
||||
return typeToArgName(t.Elem())
|
||||
case *types.Chan:
|
||||
return "ch"
|
||||
}
|
||||
|
||||
s = strings.TrimFunc(s, func(r rune) bool {
|
||||
return !unicode.IsLetter(r)
|
||||
})
|
||||
|
||||
if s == "error" {
|
||||
return "err"
|
||||
}
|
||||
|
||||
// remove package (if present)
|
||||
// and make first letter lowercase
|
||||
a := []rune(s[strings.LastIndexByte(s, '.')+1:])
|
||||
a[0] = unicode.ToLower(a[0])
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// isCallPosition reports whether the path denotes the subtree in call position, f().
|
||||
func isCallPosition(path []ast.Node) bool {
|
||||
return len(path) > 1 &&
|
||||
is[*ast.CallExpr](path[1]) &&
|
||||
path[1].(*ast.CallExpr).Fun == path[0]
|
||||
}
|
||||
|
||||
func is[T any](x any) bool {
|
||||
_, ok := x.(T)
|
||||
return ok
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 undeclaredname_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/undeclaredname"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, undeclaredname.Analyzer, "a")
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
|
||||
// The unusedparams command runs the unusedparams analyzer.
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/analysis/singlechecker"
|
||||
"golang.org/x/tools/gopls/internal/analysis/unusedparams"
|
||||
)
|
||||
|
||||
func main() { singlechecker.Main(unusedparams.Analyzer) }
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
// Package unusedparams defines an analyzer that checks for unused
|
||||
// parameters of functions.
|
||||
//
|
||||
// # Analyzer unusedparams
|
||||
//
|
||||
// unusedparams: check for unused parameters of functions
|
||||
//
|
||||
// The unusedparams analyzer checks functions to see if there are
|
||||
// any parameters that are not being used.
|
||||
//
|
||||
// To ensure soundness, it ignores:
|
||||
// - "address-taken" functions, that is, functions that are used as
|
||||
// a value rather than being called directly; their signatures may
|
||||
// be required to conform to a func type.
|
||||
// - exported functions or methods, since they may be address-taken
|
||||
// in another package.
|
||||
// - unexported methods whose name matches an interface method
|
||||
// declared in the same package, since the method's signature
|
||||
// may be required to conform to the interface type.
|
||||
// - functions with empty bodies, or containing just a call to panic.
|
||||
// - parameters that are unnamed, or named "_", the blank identifier.
|
||||
//
|
||||
// The analyzer suggests a fix of replacing the parameter name by "_",
|
||||
// but in such cases a deeper fix can be obtained by invoking the
|
||||
// "Refactor: remove unused parameter" code action, which will
|
||||
// eliminate the parameter entirely, along with all corresponding
|
||||
// arguments at call sites, while taking care to preserve any side
|
||||
// effects in the argument expressions; see
|
||||
// https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.
|
||||
package unusedparams
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package a
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type parent interface {
|
||||
n(f bool)
|
||||
}
|
||||
|
||||
type yuh struct {
|
||||
a int
|
||||
}
|
||||
|
||||
func (y *yuh) n(f bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
func a(i1 int, i2 int, i3 int) int { // want "unused parameter: i2"
|
||||
i3 += i1
|
||||
_ = func(z int) int { // want "unused parameter: z"
|
||||
_ = 1
|
||||
return 1
|
||||
}
|
||||
return i3
|
||||
}
|
||||
|
||||
func b(c bytes.Buffer) { // want "unused parameter: c"
|
||||
_ = 1
|
||||
}
|
||||
|
||||
func z(h http.ResponseWriter, _ *http.Request) { // no report: func z is address-taken
|
||||
fmt.Println("Before")
|
||||
}
|
||||
|
||||
func l(h http.Handler) http.Handler { // want "unused parameter: h"
|
||||
return http.HandlerFunc(z)
|
||||
}
|
||||
|
||||
func mult(a, b int) int { // want "unused parameter: b"
|
||||
a += 1
|
||||
return a
|
||||
}
|
||||
|
||||
func y(a int) {
|
||||
panic("yo")
|
||||
}
|
||||
|
||||
var _ = func(x int) {} // empty body: no diagnostic
|
||||
|
||||
var _ = func(x int) { println() } // want "unused parameter: x"
|
||||
|
||||
var (
|
||||
calledGlobal = func(x int) { println() } // want "unused parameter: x"
|
||||
addressTakenGlobal = func(x int) { println() } // no report: function is address-taken
|
||||
)
|
||||
|
||||
func _() {
|
||||
calledGlobal(1)
|
||||
println(addressTakenGlobal)
|
||||
}
|
||||
|
||||
func Exported(unused int) {} // no finding: an exported function may be address-taken
|
||||
|
||||
type T int
|
||||
|
||||
func (T) m(f bool) { println() } // want "unused parameter: f"
|
||||
func (T) n(f bool) { println() } // no finding: n may match the interface method parent.n
|
||||
|
||||
func _() {
|
||||
var fib func(x, y int) int
|
||||
fib = func(x, y int) int { // want "unused parameter: y"
|
||||
if x < 2 {
|
||||
return x
|
||||
}
|
||||
return fib(x-1, 123) + fib(x-2, 456)
|
||||
}
|
||||
fib(10, 42)
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package a
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type parent interface {
|
||||
n(f bool)
|
||||
}
|
||||
|
||||
type yuh struct {
|
||||
a int
|
||||
}
|
||||
|
||||
func (y *yuh) n(f bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
func a(i1 int, _ int, i3 int) int { // want "unused parameter: i2"
|
||||
i3 += i1
|
||||
_ = func(_ int) int { // want "unused parameter: z"
|
||||
_ = 1
|
||||
return 1
|
||||
}
|
||||
return i3
|
||||
}
|
||||
|
||||
func b(_ bytes.Buffer) { // want "unused parameter: c"
|
||||
_ = 1
|
||||
}
|
||||
|
||||
func z(h http.ResponseWriter, _ *http.Request) { // no report: func z is address-taken
|
||||
fmt.Println("Before")
|
||||
}
|
||||
|
||||
func l(_ http.Handler) http.Handler { // want "unused parameter: h"
|
||||
return http.HandlerFunc(z)
|
||||
}
|
||||
|
||||
func mult(a, _ int) int { // want "unused parameter: b"
|
||||
a += 1
|
||||
return a
|
||||
}
|
||||
|
||||
func y(a int) {
|
||||
panic("yo")
|
||||
}
|
||||
|
||||
var _ = func(x int) {} // empty body: no diagnostic
|
||||
|
||||
var _ = func(_ int) { println() } // want "unused parameter: x"
|
||||
|
||||
var (
|
||||
calledGlobal = func(_ int) { println() } // want "unused parameter: x"
|
||||
addressTakenGlobal = func(x int) { println() } // no report: function is address-taken
|
||||
)
|
||||
|
||||
func _() {
|
||||
calledGlobal(1)
|
||||
println(addressTakenGlobal)
|
||||
}
|
||||
|
||||
func Exported(unused int) {} // no finding: an exported function may be address-taken
|
||||
|
||||
type T int
|
||||
|
||||
func (T) m(_ bool) { println() } // want "unused parameter: f"
|
||||
func (T) n(f bool) { println() } // no finding: n may match the interface method parent.n
|
||||
|
||||
func _() {
|
||||
var fib func(x, y int) int
|
||||
fib = func(x, _ int) int { // want "unused parameter: y"
|
||||
if x < 2 {
|
||||
return x
|
||||
}
|
||||
return fib(x-1, 123) + fib(x-2, 456)
|
||||
}
|
||||
fib(10, 42)
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type parent[T any] interface {
|
||||
n(f T)
|
||||
}
|
||||
|
||||
type yuh[T any] struct {
|
||||
a T
|
||||
}
|
||||
|
||||
func (y *yuh[int]) n(f bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
func a[T comparable](i1 int, i2 T, i3 int) int { // want "unused parameter: i2"
|
||||
i3 += i1
|
||||
_ = func(z int) int { // want "unused parameter: z"
|
||||
_ = 1
|
||||
return 1
|
||||
}
|
||||
return i3
|
||||
}
|
||||
|
||||
func b[T any](c bytes.Buffer) { // want "unused parameter: c"
|
||||
_ = 1
|
||||
}
|
||||
|
||||
func z[T http.ResponseWriter](h T, _ *http.Request) { // no report: func z is address-taken
|
||||
fmt.Println("Before")
|
||||
}
|
||||
|
||||
func l(h http.Handler) http.Handler { // want "unused parameter: h"
|
||||
return http.HandlerFunc(z[http.ResponseWriter])
|
||||
}
|
||||
|
||||
func mult(a, b int) int { // want "unused parameter: b"
|
||||
a += 1
|
||||
return a
|
||||
}
|
||||
|
||||
func y[T any](a T) {
|
||||
panic("yo")
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typeparams
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type parent[T any] interface {
|
||||
n(f T)
|
||||
}
|
||||
|
||||
type yuh[T any] struct {
|
||||
a T
|
||||
}
|
||||
|
||||
func (y *yuh[int]) n(f bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
func a[T comparable](i1 int, _ T, i3 int) int { // want "unused parameter: i2"
|
||||
i3 += i1
|
||||
_ = func(_ int) int { // want "unused parameter: z"
|
||||
_ = 1
|
||||
return 1
|
||||
}
|
||||
return i3
|
||||
}
|
||||
|
||||
func b[T any](_ bytes.Buffer) { // want "unused parameter: c"
|
||||
_ = 1
|
||||
}
|
||||
|
||||
func z[T http.ResponseWriter](h T, _ *http.Request) { // no report: func z is address-taken
|
||||
fmt.Println("Before")
|
||||
}
|
||||
|
||||
func l(_ http.Handler) http.Handler { // want "unused parameter: h"
|
||||
return http.HandlerFunc(z[http.ResponseWriter])
|
||||
}
|
||||
|
||||
func mult(a, _ int) int { // want "unused parameter: b"
|
||||
a += 1
|
||||
return a
|
||||
}
|
||||
|
||||
func y[T any](a T) {
|
||||
panic("yo")
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
// Copyright 2020 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 unusedparams
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"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/gopls/internal/util/slices"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
)
|
||||
|
||||
//go:embed doc.go
|
||||
var doc string
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "unusedparams",
|
||||
Doc: analysisinternal.MustExtractDoc(doc, "unusedparams"),
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams",
|
||||
}
|
||||
|
||||
const FixCategory = "unusedparams" // recognized by gopls ApplyFix
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
// First find all "address-taken" functions.
|
||||
// We must conservatively assume that their parameters
|
||||
// are all required to conform to some signature.
|
||||
//
|
||||
// A named function is address-taken if it is somewhere
|
||||
// used not in call position:
|
||||
//
|
||||
// f(...) // not address-taken
|
||||
// use(f) // address-taken
|
||||
//
|
||||
// A literal function is address-taken if it is not
|
||||
// immediately bound to a variable, or if that variable is
|
||||
// used not in call position:
|
||||
//
|
||||
// f := func() { ... }; f() used only in call position
|
||||
// var f func(); f = func() { ...f()... }; f() ditto
|
||||
// use(func() { ... }) address-taken
|
||||
//
|
||||
|
||||
// Note: this algorithm relies on the assumption that the
|
||||
// analyzer is called only for the "widest" package for a
|
||||
// given file: that is, p_test in preference to p, if both
|
||||
// exist. Analyzing only package p may produce diagnostics
|
||||
// that would be falsified based on declarations in p_test.go
|
||||
// files. The gopls analysis driver does this, but most
|
||||
// drivers to not, so running this command in, say,
|
||||
// unitchecker or multichecker may produce incorrect results.
|
||||
|
||||
// Gather global information:
|
||||
// - uses of functions not in call position
|
||||
// - unexported interface methods
|
||||
// - all referenced variables
|
||||
|
||||
usesOutsideCall := make(map[types.Object][]*ast.Ident)
|
||||
unexportedIMethodNames := make(map[string]bool)
|
||||
{
|
||||
callPosn := make(map[*ast.Ident]bool) // all idents f appearing in f() calls
|
||||
filter := []ast.Node{
|
||||
(*ast.CallExpr)(nil),
|
||||
(*ast.InterfaceType)(nil),
|
||||
}
|
||||
inspect.Preorder(filter, func(n ast.Node) {
|
||||
switch n := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
// Strip off any generic instantiation.
|
||||
fun := n.Fun
|
||||
switch fun_ := fun.(type) {
|
||||
case *ast.IndexExpr:
|
||||
fun = fun_.X // f[T]() (funcs[i]() is rejected below)
|
||||
case *ast.IndexListExpr:
|
||||
fun = fun_.X // f[K, V]()
|
||||
}
|
||||
|
||||
// Find object:
|
||||
// record non-exported function, method, or func-typed var.
|
||||
var id *ast.Ident
|
||||
switch fun := fun.(type) {
|
||||
case *ast.Ident:
|
||||
id = fun
|
||||
case *ast.SelectorExpr:
|
||||
id = fun.Sel
|
||||
}
|
||||
if id != nil && !id.IsExported() {
|
||||
switch pass.TypesInfo.Uses[id].(type) {
|
||||
case *types.Func, *types.Var:
|
||||
callPosn[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.InterfaceType:
|
||||
// Record the set of names of unexported interface methods.
|
||||
// (It would be more precise to record signatures but
|
||||
// generics makes it tricky, and this conservative
|
||||
// heuristic is close enough.)
|
||||
t := pass.TypesInfo.TypeOf(n).(*types.Interface)
|
||||
for i := 0; i < t.NumExplicitMethods(); i++ {
|
||||
m := t.ExplicitMethod(i)
|
||||
if !m.Exported() && m.Name() != "_" {
|
||||
unexportedIMethodNames[m.Name()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for id, obj := range pass.TypesInfo.Uses {
|
||||
if !callPosn[id] {
|
||||
// This includes "f = func() {...}", which we deal with below.
|
||||
usesOutsideCall[obj] = append(usesOutsideCall[obj], id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all vars (notably parameters) that are used.
|
||||
usedVars := make(map[*types.Var]bool)
|
||||
for _, obj := range pass.TypesInfo.Uses {
|
||||
if v, ok := obj.(*types.Var); ok {
|
||||
if v.IsField() {
|
||||
continue // no point gathering these
|
||||
}
|
||||
usedVars[v] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each non-address-taken function's parameters are all used.
|
||||
filter := []ast.Node{
|
||||
(*ast.FuncDecl)(nil),
|
||||
(*ast.FuncLit)(nil),
|
||||
}
|
||||
inspect.WithStack(filter, func(n ast.Node, push bool, stack []ast.Node) bool {
|
||||
// (We always return true so that we visit nested FuncLits.)
|
||||
|
||||
if !push {
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
fn types.Object // function symbol (*Func, possibly *Var for a FuncLit)
|
||||
ftype *ast.FuncType
|
||||
body *ast.BlockStmt
|
||||
)
|
||||
switch n := n.(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
|
||||
|
||||
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 := stack[len(stack)-2].(type) {
|
||||
case *ast.AssignStmt:
|
||||
// f = func() {...}
|
||||
// f := func() {...}
|
||||
for i, rhs := range parent.Rhs {
|
||||
if rhs == n {
|
||||
if id, ok := parent.Lhs[i].(*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] = slices.Remove(usesOutsideCall[fn], id)
|
||||
}
|
||||
|
||||
if fn == nil && id.Name == "_" {
|
||||
// Edge case: _ = func() {...}
|
||||
// has no var. Fake one.
|
||||
fn = types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n))
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// If there are no parameters, there are no unused parameters.
|
||||
if ftype.Params.NumFields() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
// 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 nil, nil
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 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 unusedparams_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/gopls/internal/analysis/unusedparams"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.RunWithSuggestedFixes(t, testdata, unusedparams.Analyzer, "a", "typeparams")
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package a
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type A struct {
|
||||
b int
|
||||
}
|
||||
|
||||
func singleAssignment() {
|
||||
v := "s" // want `declared (and|but) not used`
|
||||
|
||||
s := []int{ // want `declared (and|but) not used`
|
||||
1,
|
||||
2,
|
||||
}
|
||||
|
||||
a := func(s string) bool { // want `declared (and|but) not used`
|
||||
return false
|
||||
}
|
||||
|
||||
if 1 == 1 {
|
||||
s := "v" // want `declared (and|but) not used`
|
||||
}
|
||||
|
||||
panic("I should survive")
|
||||
}
|
||||
|
||||
func noOtherStmtsInBlock() {
|
||||
v := "s" // want `declared (and|but) not used`
|
||||
}
|
||||
|
||||
func partOfMultiAssignment() {
|
||||
f, err := os.Open("file") // want `declared (and|but) not used`
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func sideEffects(cBool chan bool, cInt chan int) {
|
||||
b := <-c // want `declared (and|but) not used`
|
||||
s := fmt.Sprint("") // want `declared (and|but) not used`
|
||||
a := A{ // want `declared (and|but) not used`
|
||||
b: func() int {
|
||||
return 1
|
||||
}(),
|
||||
}
|
||||
c := A{<-cInt} // want `declared (and|but) not used`
|
||||
d := fInt() + <-cInt // want `declared (and|but) not used`
|
||||
e := fBool() && <-cBool // want `declared (and|but) not used`
|
||||
f := map[int]int{ // want `declared (and|but) not used`
|
||||
fInt(): <-cInt,
|
||||
}
|
||||
g := []int{<-cInt} // want `declared (and|but) not used`
|
||||
h := func(s string) {} // want `declared (and|but) not used`
|
||||
i := func(s string) {}() // want `declared (and|but) not used`
|
||||
}
|
||||
|
||||
func commentAbove() {
|
||||
// v is a variable
|
||||
v := "s" // want `declared (and|but) not used`
|
||||
}
|
||||
|
||||
func fBool() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func fInt() int {
|
||||
return 1
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package a
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type A struct {
|
||||
b int
|
||||
}
|
||||
|
||||
func singleAssignment() {
|
||||
if 1 == 1 {
|
||||
}
|
||||
|
||||
panic("I should survive")
|
||||
}
|
||||
|
||||
func noOtherStmtsInBlock() {
|
||||
}
|
||||
|
||||
func partOfMultiAssignment() {
|
||||
_, err := os.Open("file") // want `declared (and|but) not used`
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func sideEffects(cBool chan bool, cInt chan int) {
|
||||
<-c // want `declared (and|but) not used`
|
||||
fmt.Sprint("") // want `declared (and|but) not used`
|
||||
A{ // want `declared (and|but) not used`
|
||||
b: func() int {
|
||||
return 1
|
||||
}(),
|
||||
}
|
||||
A{<-cInt} // want `declared (and|but) not used`
|
||||
fInt() + <-cInt // want `declared (and|but) not used`
|
||||
fBool() && <-cBool // want `declared (and|but) not used`
|
||||
map[int]int{ // want `declared (and|but) not used`
|
||||
fInt(): <-cInt,
|
||||
}
|
||||
[]int{<-cInt} // want `declared (and|but) not used`
|
||||
func(s string) {}() // want `declared (and|but) not used`
|
||||
}
|
||||
|
||||
func commentAbove() {
|
||||
// v is a variable
|
||||
}
|
||||
|
||||
func fBool() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func fInt() int {
|
||||
return 1
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package decl
|
||||
|
||||
func a() {
|
||||
var b, c bool // want `declared (and|but) not used`
|
||||
panic(c)
|
||||
|
||||
if 1 == 1 {
|
||||
var s string // want `declared (and|but) not used`
|
||||
}
|
||||
}
|
||||
|
||||
func b() {
|
||||
// b is a variable
|
||||
var b bool // want `declared (and|but) not used`
|
||||
}
|
||||
|
||||
func c() {
|
||||
var (
|
||||
d string
|
||||
|
||||
// some comment for c
|
||||
c bool // want `declared (and|but) not used`
|
||||
)
|
||||
|
||||
panic(d)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user