internal/lsp/analysis: move implementmissing logic into undeclared
I missed the TODO in undeclaredname to add support for functions, so
really this belongs in that analyzer. This removes a fair bit of code.
However, the type error analyzers don't really work with the go/analysis
testing framework because the suggested fixes are split from the
diagnostics, so I moved the tests into the gopls tests.
Change-Id: I861a7ad531d2732fe698ee0ac46f23ad53b16812
Reviewed-on: https://go-review.googlesource.com/c/tools/+/351333
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 8215f1b..cc344b8 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -579,25 +579,6 @@
**Enabled by default.**
-## **implementmissing**
-
-suggested fixes for "undeclared name: %s" on a function call
-
-This checker provides suggested fixes for type errors of the
-type "undeclared name: %s" that happen for a function call. For example:
- func m() {
- a(1)
- }
-will turn into
- func m() {
- a(1)
- }
-
- func a(i int) {}
-
-
-**Disabled by default. Enable it by setting `"analyses": {"implementmissing": true}`.**
-
## **nonewvars**
suggested fixes for "no new vars on left side of :="
@@ -631,8 +612,17 @@
suggested fixes for "undeclared name: <>"
This checker provides suggested fixes for type errors of the
-type "undeclared name: <>". It will insert a new statement:
-"<> := ".
+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!")
+}
+
**Enabled by default.**
diff --git a/internal/lsp/analysis/implementmissing/missingfunc.go b/internal/lsp/analysis/implementmissing/missingfunc.go
deleted file mode 100644
index 2e4bc3c..0000000
--- a/internal/lsp/analysis/implementmissing/missingfunc.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// 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 implementmissing defines an Analyzer that will attempt to
-// automatically implement a function that is currently undeclared.
-package implementmissing
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/format"
- "go/types"
- "strings"
- "unicode"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/analysisinternal"
-)
-
-const Doc = `suggested fixes for "undeclared name: %s" on a function call
-
-This checker provides suggested fixes for type errors of the
-type "undeclared name: %s" that happen for a function call. For example:
- func m() {
- a(1)
- }
-will turn into
- func m() {
- a(1)
- }
-
- func a(i int) {}
-`
-
-var Analyzer = &analysis.Analyzer{
- Name: "implementmissing",
- Doc: Doc,
- Requires: []*analysis.Analyzer{},
- Run: run,
- RunDespiteErrors: true,
-}
-
-const undeclaredNamePrefix = "undeclared name: "
-
-func run(pass *analysis.Pass) (interface{}, error) {
- info := pass.TypesInfo
- if info == nil {
- return nil, fmt.Errorf("nil TypeInfo")
- }
-
- errors := analysisinternal.GetTypeErrors(pass)
- for _, typeErr := range errors {
- // Filter out the errors that are not relevant to this analyzer.
- if !FixesError(typeErr.Msg) {
- 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
- }
-
- var buf bytes.Buffer
- if err := format.Node(&buf, pass.Fset, file); err != nil {
- continue
- }
- typeErrEndPos := analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), typeErr.Pos)
-
- // Get the path for the relevant range.
- path, _ := astutil.PathEnclosingInterval(file, typeErr.Pos, typeErrEndPos)
- if len(path) < 2 {
- return nil, nil
- }
-
- // Check to make sure we're dealing with a function call, we don't want to
- // deal with undeclared variables here.
- call, ok := path[1].(*ast.CallExpr)
- if !ok {
- return nil, nil
- }
-
- ident, ok := path[0].(*ast.Ident)
- if !ok {
- return nil, nil
- }
-
- var paramNames []string
- var paramTypes []types.Type
-
- // keep track of all param names to later ensure uniqueness
- namesCount := map[string]int{}
-
- for _, arg := range call.Args {
- ty := pass.TypesInfo.TypeOf(arg)
- if ty == nil {
- return nil, nil
- }
-
- switch t := ty.(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())
- namesCount[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(ty)
- }
-
- namesCount[name]++
-
- paramNames = append(paramNames, name)
- paramTypes = append(paramTypes, types.Default(ty))
- }
- }
-
- for n, c := range namesCount {
- // 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 {
- namesCount[n] = 1
- } else {
- delete(namesCount, n)
- }
- }
-
- params := &ast.FieldList{
- List: []*ast.Field{},
- }
-
- for i, name := range paramNames {
- if suffix, repeats := namesCount[name]; repeats {
- namesCount[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(pass.Fset, file, pass.Pkg, paramTypes[i]),
- })
- }
-
- eof := file.End()
-
- decl := &ast.FuncDecl{
- Name: &ast.Ident{
- Name: ident.Name,
- },
- Type: &ast.FuncType{
- Func: file.End(),
- Params: params,
- Results: &ast.FieldList{},
- },
- Body: &ast.BlockStmt{
- List: []ast.Stmt{
- &ast.ExprStmt{
- X: &ast.CallExpr{
- Fun: &ast.Ident{
- Name: "panic",
- },
- Args: []ast.Expr{
- &ast.BasicLit{
- Value: `"not implemented"`,
- },
- },
- },
- },
- },
- },
- }
-
- var declBuf bytes.Buffer
- if err := format.Node(&declBuf, pass.Fset, decl); err != nil {
- return nil, err
- }
-
- text := append([]byte("\n\n"), declBuf.Bytes()...)
- text = append(text, []byte("\n")...)
-
- pass.Report(analysis.Diagnostic{
- Pos: typeErr.Pos,
- End: typeErr.Pos,
- Message: typeErr.Msg,
- SuggestedFixes: []analysis.SuggestedFix{
- {
- Message: "Implement function " + ident.Name,
- TextEdits: []analysis.TextEdit{{
- Pos: eof,
- End: eof,
- NewText: text,
- }},
- },
- },
- Related: []analysis.RelatedInformation{},
- })
- }
- return nil, nil
-}
-
-func FixesError(msg string) bool {
- return strings.HasPrefix(msg, undeclaredNamePrefix)
-}
-
-func typeToArgName(ty types.Type) string {
- s := types.Default(ty).String()
-
- switch t := 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
- parts := strings.Split(s, ".")
- a := []rune(parts[len(parts)-1])
- a[0] = unicode.ToLower(a[0])
- return string(a)
-}
diff --git a/internal/lsp/analysis/implementmissing/missingfunc_test.go b/internal/lsp/analysis/implementmissing/missingfunc_test.go
deleted file mode 100644
index 7c9b573..0000000
--- a/internal/lsp/analysis/implementmissing/missingfunc_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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 implementmissing_test
-
-import (
- "testing"
-
- "golang.org/x/tools/go/analysis/analysistest"
- "golang.org/x/tools/internal/lsp/analysis/implementmissing"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- analysistest.RunWithSuggestedFixes(t, testdata, implementmissing.Analyzer, "missingfunction")
-}
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden
deleted file mode 100644
index c8578e6..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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 missingfunction
-
-func channels(s string) {
- undefinedChannels(c()) // want "undeclared name: undefinedChannels"
-}
-
-func c() (<-chan string, chan string) {
- return make(<-chan string), make(chan string)
-}
-
-func undefinedChannels(ch1 <-chan string, ch2 chan string) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden
deleted file mode 100644
index 050b0fc..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 missingfunction
-
-func consecutiveParams() {
- var s string
- undefinedConsecutiveParams(s, s) // want "undeclared name: undefinedConsecutiveParams"
-}
-
-func undefinedConsecutiveParams(s1, s2 string) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden
deleted file mode 100644
index 1661fc4..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 missingfunction
-
-func errorParam() {
- var err error
- undefinedErrorParam(err) // want "undeclared name: undefinedErrorParam"
-}
-
-func undefinedErrorParam(err error) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden
deleted file mode 100644
index ee32b71..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 missingfunction
-
-type T struct{}
-
-func literals() {
- undefinedLiterals("hey compiler", T{}, &T{}) // want "undeclared name: undefinedLiterals"
-}
-
-func undefinedLiterals(s string, t1 T, t2 *T) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden
deleted file mode 100644
index 1bf35cf..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 missingfunction
-
-import "time"
-
-func operation() {
- undefinedOperation(10 * time.Second) // want "undeclared name: undefinedOperation"
-}
-
-func undefinedOperation(duration time.Duration) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden
deleted file mode 100644
index 9d66f6a..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 missingfunction
-
-func selector() {
- m := map[int]bool{}
- undefinedSelector(m[1]) // want "undeclared name: undefinedSelector"
-}
-
-func undefinedSelector(b bool) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden
deleted file mode 100644
index 75f4578..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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 missingfunction
-
-func slice() {
- undefinedSlice([]int{1, 2}) // want "undeclared name: undefinedSlice"
-}
-
-func undefinedSlice(i []int) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden
deleted file mode 100644
index ac33ed0..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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 missingfunction
-
-func tuple() {
- undefinedTuple(b()) // want "undeclared name: undefinedTuple"
-}
-
-func b() (string, error) {
- return "", nil
-}
-
-func undefinedTuple(s string, err error) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden
deleted file mode 100644
index 28c27d8..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 missingfunction
-
-func uniqueArguments() {
- var s string
- var i int
- undefinedUniqueArguments(s, i, s) // want "undeclared name: undefinedUniqueArguments"
-}
-
-func undefinedUniqueArguments(s1 string, i int, s2 string) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
similarity index 93%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
index fa91518..ecf00ec 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func channels(s string) {
undefinedChannels(c()) // want "undeclared name: undefinedChannels"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
similarity index 92%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
index 4a6ec62..ab7b2ba 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func consecutiveParams() {
var s string
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
index 49c5258..341a9d2 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func errorParam() {
var err error
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
index 46c045c..ab82463 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
type T struct{}
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
index b8330ec..9a54382 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
import "time"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
index 5db716f..9ed09a2 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func selector() {
m := map[int]bool{}
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
similarity index 90%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
index 179954c..d741c68 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func slice() {
undefinedSlice([]int{1, 2}) // want "undeclared name: undefinedSlice"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
index 673f607..3148e8f 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func tuple() {
undefinedTuple(b()) // want "undeclared name: undefinedTuple"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
similarity index 92%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
index 2f18498..98f77a4 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package missingfunction
+package undeclared
func uniqueArguments() {
var s string
diff --git a/internal/lsp/analysis/undeclaredname/undeclared.go b/internal/lsp/analysis/undeclaredname/undeclared.go
index df24d1d..1e47706 100644
--- a/internal/lsp/analysis/undeclaredname/undeclared.go
+++ b/internal/lsp/analysis/undeclaredname/undeclared.go
@@ -10,9 +10,11 @@
"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"
@@ -23,8 +25,17 @@
const Doc = `suggested fixes for "undeclared name: <>"
This checker provides suggested fixes for type errors of the
-type "undeclared name: <>". It will insert a new statement:
-"<> := ".`
+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!")
+}
+`
var Analyzer = &analysis.Analyzer{
Name: string(analysisinternal.UndeclaredName),
@@ -67,11 +78,6 @@
if _, ok := path[1].(*ast.SelectorExpr); ok {
continue
}
- // TODO(golang.org/issue/34644): Handle call expressions with suggested
- // fixes to create a function.
- if _, ok := path[1].(*ast.CallExpr); ok {
- continue
- }
tok := pass.Fset.File(file.Pos())
if tok == nil {
continue
@@ -87,7 +93,7 @@
return nil, nil
}
-func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, _ *types.Package, _ *types.Info) (*analysis.SuggestedFix, error) {
+func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
pos := rng.Start // don't use the end
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if len(path) < 2 {
@@ -97,6 +103,15 @@
if !ok {
return nil, fmt.Errorf("")
}
+
+ // Check for a possible call expression, in which case we should add a
+ // new function declaration.
+ if len(path) > 1 {
+ if _, ok := path[1].(*ast.CallExpr); ok {
+ return newFunctionDeclaration(path, file, pkg, info, fset)
+ }
+ }
+
// Get the place to insert the new statement.
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
if insertBeforeStmt == nil {
@@ -111,6 +126,7 @@
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 &analysis.SuggestedFix{
@@ -126,3 +142,182 @@
func FixesError(msg string) bool {
return strings.HasPrefix(msg, undeclaredNamePrefix)
}
+
+func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*analysis.SuggestedFix, error) {
+ if len(path) < 3 {
+ return nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)
+ }
+ ident, ok := path[0].(*ast.Ident)
+ if !ok {
+ return nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0])
+ }
+ call, ok := path[1].(*ast.CallExpr)
+ if !ok {
+ return 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, 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, 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(fset, file, pkg, paramTypes[i]),
+ })
+ }
+
+ decl := &ast.FuncDecl{
+ Name: ast.NewIdent(ident.Name),
+ Type: &ast.FuncType{
+ Params: params,
+ // TODO(rstambler): Also handle result parameters here.
+ },
+ 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, err
+ }
+ return &analysis.SuggestedFix{
+ Message: fmt.Sprintf("Create function \"%s\"", 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 := 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)
+}
diff --git a/internal/lsp/cmd/test/suggested_fix.go b/internal/lsp/cmd/test/suggested_fix.go
index 160dcdf..c819e05 100644
--- a/internal/lsp/cmd/test/suggested_fix.go
+++ b/internal/lsp/cmd/test/suggested_fix.go
@@ -24,7 +24,7 @@
args = append(args, actionKinds...)
got, stderr := r.NormalizeGoplsCmd(t, args...)
if stderr == "ExecuteCommand is not yet supported on the command line" {
- t.Skipf(stderr)
+ return // don't skip to keep the summary counts correct
}
want := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), filename, func() ([]byte, error) {
return []byte(got), nil
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index b78b57d..59eb437 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -562,11 +562,6 @@
Default: "true",
},
{
- Name: "\"implementmissing\"",
- Doc: "suggested fixes for \"undeclared name: %s\" on a function call\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: %s\" that happen for a function call. For example:\n\tfunc m() {\n\t a(1)\n\t}\nwill turn into\n\tfunc m() {\n\t a(1)\n\t}\n\n\tfunc a(i int) {}\n",
- Default: "false",
- },
- {
Name: "\"nonewvars\"",
Doc: "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\tz := 1\n\tz := 2\nwill turn into\n\tz := 1\n\tz = 2\n",
Default: "true",
@@ -578,7 +573,7 @@
},
{
Name: "\"undeclaredname\"",
- Doc: "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will insert a new statement:\n\"<> := \".",
+ Doc: "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will either insert a new statement,\nsuch as:\n\n\"<> := \"\n\nor a new function declaration, such as:\n\nfunc <>(inferred parameters) {\n\tpanic(\"implement me!\")\n}\n",
Default: "true",
},
{
@@ -1140,11 +1135,6 @@
Default: true,
},
{
- Name: "implementmissing",
- Doc: "suggested fixes for \"undeclared name: %s\" on a function call\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: %s\" that happen for a function call. For example:\n\tfunc m() {\n\t a(1)\n\t}\nwill turn into\n\tfunc m() {\n\t a(1)\n\t}\n\n\tfunc a(i int) {}\n",
- Default: false,
- },
- {
Name: "nonewvars",
Doc: "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\tz := 1\n\tz := 2\nwill turn into\n\tz := 1\n\tz = 2\n",
Default: true,
@@ -1156,7 +1146,7 @@
},
{
Name: "undeclaredname",
- Doc: "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will insert a new statement:\n\"<> := \".",
+ Doc: "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will either insert a new statement,\nsuch as:\n\n\"<> := \"\n\nor a new function declaration, such as:\n\nfunc <>(inferred parameters) {\n\tpanic(\"implement me!\")\n}\n",
Default: true,
},
{
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 1efe177..374fb70 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -49,7 +49,6 @@
"golang.org/x/tools/go/analysis/passes/unusedwrite"
"golang.org/x/tools/internal/lsp/analysis/fillreturns"
"golang.org/x/tools/internal/lsp/analysis/fillstruct"
- "golang.org/x/tools/internal/lsp/analysis/implementmissing"
"golang.org/x/tools/internal/lsp/analysis/nonewvars"
"golang.org/x/tools/internal/lsp/analysis/noresultvalues"
"golang.org/x/tools/internal/lsp/analysis/simplifycompositelit"
@@ -756,9 +755,6 @@
if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok {
o.Analyses[unusedparams.Analyzer.Name] = true
}
- if _, ok := o.Analyses[implementmissing.Analyzer.Name]; !ok {
- o.Analyses[implementmissing.Analyzer.Name] = true
- }
}
func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult {
@@ -1176,11 +1172,6 @@
ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
Enabled: true,
},
- implementmissing.Analyzer.Name: {
- Analyzer: implementmissing.Analyzer,
- ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- Enabled: false,
- },
nonewvars.Analyzer.Name: {
Analyzer: nonewvars.Analyzer,
Enabled: true,
diff --git a/internal/lsp/testdata/missingfunction/channels.go b/internal/lsp/testdata/missingfunction/channels.go
new file mode 100644
index 0000000..436491c
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/channels.go
@@ -0,0 +1,9 @@
+package missingfunction
+
+func channels(s string) {
+ undefinedChannels(c()) //@suggestedfix("undefinedChannels", "quickfix")
+}
+
+func c() (<-chan string, chan string) {
+ return make(<-chan string), make(chan string)
+}
diff --git a/internal/lsp/testdata/missingfunction/channels.go.golden b/internal/lsp/testdata/missingfunction/channels.go.golden
new file mode 100644
index 0000000..f5078fe
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/channels.go.golden
@@ -0,0 +1,15 @@
+-- suggestedfix_channels_4_2 --
+package missingfunction
+
+func channels(s string) {
+ undefinedChannels(c()) //@suggestedfix("undefinedChannels", "quickfix")
+}
+
+func undefinedChannels(ch1 <-chan string, ch2 chan string) {
+ panic("unimplemented")
+}
+
+func c() (<-chan string, chan string) {
+ return make(<-chan string), make(chan string)
+}
+
diff --git a/internal/lsp/testdata/missingfunction/consecutive_params.go b/internal/lsp/testdata/missingfunction/consecutive_params.go
new file mode 100644
index 0000000..d2ec3be
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/consecutive_params.go
@@ -0,0 +1,6 @@
+package missingfunction
+
+func consecutiveParams() {
+ var s string
+ undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/consecutive_params.go.golden b/internal/lsp/testdata/missingfunction/consecutive_params.go.golden
new file mode 100644
index 0000000..14a7664
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/consecutive_params.go.golden
@@ -0,0 +1,12 @@
+-- suggestedfix_consecutive_params_5_2 --
+package missingfunction
+
+func consecutiveParams() {
+ var s string
+ undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", "quickfix")
+}
+
+func undefinedConsecutiveParams(s1, s2 string) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/error_param.go b/internal/lsp/testdata/missingfunction/error_param.go
new file mode 100644
index 0000000..9fd943f
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/error_param.go
@@ -0,0 +1,6 @@
+package missingfunction
+
+func errorParam() {
+ var err error
+ undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/error_param.go.golden b/internal/lsp/testdata/missingfunction/error_param.go.golden
new file mode 100644
index 0000000..2e12711
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/error_param.go.golden
@@ -0,0 +1,12 @@
+-- suggestedfix_error_param_5_2 --
+package missingfunction
+
+func errorParam() {
+ var err error
+ undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", "quickfix")
+}
+
+func undefinedErrorParam(err error) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/literals.go b/internal/lsp/testdata/missingfunction/literals.go
new file mode 100644
index 0000000..e276eae
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/literals.go
@@ -0,0 +1,7 @@
+package missingfunction
+
+type T struct{}
+
+func literals() {
+ undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/literals.go.golden b/internal/lsp/testdata/missingfunction/literals.go.golden
new file mode 100644
index 0000000..04782b9
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/literals.go.golden
@@ -0,0 +1,29 @@
+-- suggestedfix_literals_10_2 --
+// 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 missingfunction
+
+type T struct{}
+
+func literals() {
+ undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
+}
+
+func undefinedLiterals(s string, t1 T, t2 *T) {
+ panic("implement me!")
+}
+-- suggestedfix_literals_6_2 --
+package missingfunction
+
+type T struct{}
+
+func literals() {
+ undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
+}
+
+func undefinedLiterals(s string, t1 T, t2 *T) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/operation.go b/internal/lsp/testdata/missingfunction/operation.go
new file mode 100644
index 0000000..0408219
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/operation.go
@@ -0,0 +1,7 @@
+package missingfunction
+
+import "time"
+
+func operation() {
+ undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/operation.go.golden b/internal/lsp/testdata/missingfunction/operation.go.golden
new file mode 100644
index 0000000..5e35f30
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/operation.go.golden
@@ -0,0 +1,29 @@
+-- suggestedfix_operation_10_2 --
+// 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 missingfunction
+
+import "time"
+
+func operation() {
+ undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
+}
+
+func undefinedOperation(duration time.Duration) {
+ panic("implement me!")
+}
+-- suggestedfix_operation_6_2 --
+package missingfunction
+
+import "time"
+
+func operation() {
+ undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
+}
+
+func undefinedOperation(duration time.Duration) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/selector.go b/internal/lsp/testdata/missingfunction/selector.go
new file mode 100644
index 0000000..afd1ab6
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/selector.go
@@ -0,0 +1,6 @@
+package missingfunction
+
+func selector() {
+ m := map[int]bool{}
+ undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/selector.go.golden b/internal/lsp/testdata/missingfunction/selector.go.golden
new file mode 100644
index 0000000..c48691c
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/selector.go.golden
@@ -0,0 +1,12 @@
+-- suggestedfix_selector_5_2 --
+package missingfunction
+
+func selector() {
+ m := map[int]bool{}
+ undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", "quickfix")
+}
+
+func undefinedSelector(b bool) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/slice.go b/internal/lsp/testdata/missingfunction/slice.go
new file mode 100644
index 0000000..4a562a2
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/slice.go
@@ -0,0 +1,5 @@
+package missingfunction
+
+func slice() {
+ undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/slice.go.golden b/internal/lsp/testdata/missingfunction/slice.go.golden
new file mode 100644
index 0000000..0ccb861
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/slice.go.golden
@@ -0,0 +1,11 @@
+-- suggestedfix_slice_4_2 --
+package missingfunction
+
+func slice() {
+ undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", "quickfix")
+}
+
+func undefinedSlice(i []int) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/tuple.go b/internal/lsp/testdata/missingfunction/tuple.go
new file mode 100644
index 0000000..1c4782c
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/tuple.go
@@ -0,0 +1,9 @@
+package missingfunction
+
+func tuple() {
+ undefinedTuple(b()) //@suggestedfix("undefinedTuple", "quickfix")
+}
+
+func b() (string, error) {
+ return "", nil
+}
diff --git a/internal/lsp/testdata/missingfunction/tuple.go.golden b/internal/lsp/testdata/missingfunction/tuple.go.golden
new file mode 100644
index 0000000..1e12bb7
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/tuple.go.golden
@@ -0,0 +1,15 @@
+-- suggestedfix_tuple_4_2 --
+package missingfunction
+
+func tuple() {
+ undefinedTuple(b()) //@suggestedfix("undefinedTuple", "quickfix")
+}
+
+func undefinedTuple(s string, err error) {
+ panic("unimplemented")
+}
+
+func b() (string, error) {
+ return "", nil
+}
+
diff --git a/internal/lsp/testdata/missingfunction/unique_params.go b/internal/lsp/testdata/missingfunction/unique_params.go
new file mode 100644
index 0000000..ffaba3f
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/unique_params.go
@@ -0,0 +1,7 @@
+package missingfunction
+
+func uniqueArguments() {
+ var s string
+ var i int
+ undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/unique_params.go.golden b/internal/lsp/testdata/missingfunction/unique_params.go.golden
new file mode 100644
index 0000000..74fb91a
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/unique_params.go.golden
@@ -0,0 +1,30 @@
+-- suggestedfix_unique_params_10_2 --
+// 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 missingfunction
+
+func uniqueArguments() {
+ var s string
+ var i int
+ undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
+}
+
+func undefinedUniqueArguments(s1 string, i int, s2 string) {
+ panic("implement me!")
+}
+
+-- suggestedfix_unique_params_6_2 --
+package missingfunction
+
+func uniqueArguments() {
+ var s string
+ var i int
+ undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
+}
+
+func undefinedUniqueArguments(s1 string, i int, s2 string) {
+ panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index 7143365..9e6334a 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -13,7 +13,7 @@
FormatCount = 6
ImportCount = 8
SemanticTokenCount = 3
-SuggestedFixCount = 40
+SuggestedFixCount = 49
FunctionExtractionCount = 24
MethodExtractionCount = 6
DefinitionsCount = 95
diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden
index aaeb262..fefd3d2 100644
--- a/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -13,7 +13,7 @@
FormatCount = 6
ImportCount = 8
SemanticTokenCount = 3
-SuggestedFixCount = 40
+SuggestedFixCount = 49
FunctionExtractionCount = 24
MethodExtractionCount = 6
DefinitionsCount = 99