From b98090b833e37f88b824288c5d6d9747e1652b52 Mon Sep 17 00:00:00 2001 From: Francesco Renzi Date: Thu, 9 Sep 2021 10:19:58 +0200 Subject: [PATCH] internal/lsp/analysis/implementmissing: add analyzer This adds an analyzer that provides suggested fixes for undeclared name errors on function calls, implementing a stub of the fuction (with an empty body). As of now this doesn't try to guess returned types but only parameters. Generated functions are appended at the end of the file where these type errors occur. Updates golang/go#47558 Change-Id: Iaef45ada6b7b73de1fbe42e5f7e334512b65e6c7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/348829 Reviewed-by: Rebecca Stambler Trust: Rebecca Stambler Trust: Peter Weinberger Run-TryBot: Rebecca Stambler gopls-CI: kokoro TryBot-Result: Go Bot --- gopls/doc/analyzers.md | 19 ++ .../analysis/implementmissing/missingfunc.go | 274 ++++++++++++++++++ .../implementmissing/missingfunc_test.go | 17 ++ .../testdata/src/missingfunction/channels.go | 13 + .../src/missingfunction/channels.go.golden | 15 + .../src/missingfunction/consecutive_params.go | 10 + .../consecutive_params.go.golden | 12 + .../src/missingfunction/error_param.go | 10 + .../src/missingfunction/error_param.go.golden | 12 + .../testdata/src/missingfunction/literals.go | 11 + .../src/missingfunction/literals.go.golden | 13 + .../testdata/src/missingfunction/operation.go | 11 + .../src/missingfunction/operation.go.golden | 13 + .../testdata/src/missingfunction/selector.go | 10 + .../src/missingfunction/selector.go.golden | 12 + .../testdata/src/missingfunction/slice.go | 9 + .../src/missingfunction/slice.go.golden | 11 + .../testdata/src/missingfunction/tuple.go | 13 + .../src/missingfunction/tuple.go.golden | 15 + .../src/missingfunction/unique_params.go | 11 + .../missingfunction/unique_params.go.golden | 13 + internal/lsp/source/api_json.go | 10 + internal/lsp/source/options.go | 9 + 23 files changed, 543 insertions(+) create mode 100644 internal/lsp/analysis/implementmissing/missingfunc.go create mode 100644 internal/lsp/analysis/implementmissing/missingfunc_test.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go create mode 100644 internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md index 80ee65d73b2..c3e19aca8d9 100644 --- a/gopls/doc/analyzers.md +++ b/gopls/doc/analyzers.md @@ -573,6 +573,25 @@ This functionality is similar to https://github.com/sqs/goreturns. **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 :=" diff --git a/internal/lsp/analysis/implementmissing/missingfunc.go b/internal/lsp/analysis/implementmissing/missingfunc.go new file mode 100644 index 00000000000..2e4bc3c324f --- /dev/null +++ b/internal/lsp/analysis/implementmissing/missingfunc.go @@ -0,0 +1,274 @@ +// 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 new file mode 100644 index 00000000000..7c9b5736562 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/missingfunc_test.go @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go new file mode 100644 index 00000000000..fa9151843de --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go @@ -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 missingfunction + +func channels(s string) { + undefinedChannels(c()) // want "undeclared name: undefinedChannels" +} + +func c() (<-chan string, chan string) { + return make(<-chan string), make(chan string) +} diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden new file mode 100644 index 00000000000..c8578e68ea3 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden @@ -0,0 +1,15 @@ +// 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go new file mode 100644 index 00000000000..4a6ec62368f --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go @@ -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 missingfunction + +func consecutiveParams() { + var s string + undefinedConsecutiveParams(s, s) // want "undeclared name: undefinedConsecutiveParams" +} 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 new file mode 100644 index 00000000000..050b0fcf922 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go new file mode 100644 index 00000000000..49c5258ff9a --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go @@ -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 missingfunction + +func errorParam() { + var err error + undefinedErrorParam(err) // want "undeclared name: undefinedErrorParam" +} 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 new file mode 100644 index 00000000000..1661fc4cc3f --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go new file mode 100644 index 00000000000..46c045c3d4a --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go @@ -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 missingfunction + +type T struct{} + +func literals() { + undefinedLiterals("hey compiler", T{}, &T{}) // want "undeclared name: undefinedLiterals" +} diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden new file mode 100644 index 00000000000..ee32b710cb4 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go new file mode 100644 index 00000000000..b8330ec2c11 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go @@ -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 missingfunction + +import "time" + +func operation() { + undefinedOperation(10 * time.Second) // want "undeclared name: undefinedOperation" +} diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden new file mode 100644 index 00000000000..1bf35cf61c7 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go new file mode 100644 index 00000000000..5db716f7f65 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go @@ -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 missingfunction + +func selector() { + m := map[int]bool{} + undefinedSelector(m[1]) // want "undeclared name: undefinedSelector" +} diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden new file mode 100644 index 00000000000..9d66f6abddb --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go new file mode 100644 index 00000000000..179954cdecf --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go @@ -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 missingfunction + +func slice() { + undefinedSlice([]int{1, 2}) // want "undeclared name: undefinedSlice" +} diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden new file mode 100644 index 00000000000..75f457878a3 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden @@ -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 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go new file mode 100644 index 00000000000..673f60775ef --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go @@ -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 missingfunction + +func tuple() { + undefinedTuple(b()) // want "undeclared name: undefinedTuple" +} + +func b() (string, error) { + return "", nil +} diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden new file mode 100644 index 00000000000..ac33ed04a71 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden @@ -0,0 +1,15 @@ +// 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 b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go new file mode 100644 index 00000000000..2f18498d2a3 --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go @@ -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 missingfunction + +func uniqueArguments() { + var s string + var i int + undefinedUniqueArguments(s, i, s) // want "undeclared name: undefinedUniqueArguments" +} 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 new file mode 100644 index 00000000000..28c27d87d0a --- /dev/null +++ b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden @@ -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 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/source/api_json.go b/internal/lsp/source/api_json.go index 9b5734fa87a..4b67341363a 100755 --- a/internal/lsp/source/api_json.go +++ b/internal/lsp/source/api_json.go @@ -556,6 +556,11 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "suggested fixes for \"wrong number of return values (want %d, got %d)\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\nwill turn into\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.\n", 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", @@ -1124,6 +1129,11 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "suggested fixes for \"wrong number of return values (want %d, got %d)\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\nwill turn into\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.\n", 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", diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index 9bc73a9d7c7..19c99110417 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -49,6 +49,7 @@ import ( "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" @@ -754,6 +755,9 @@ func (o *Options) enableAllExperimentMaps() { 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 { @@ -1171,6 +1175,11 @@ func typeErrorAnalyzers() map[string]*Analyzer { 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,