aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/analysis/unusedparams/unusedparams.go
diff options
context:
space:
mode:
Diffstat (limited to 'gopls/internal/lsp/analysis/unusedparams/unusedparams.go')
-rw-r--r--gopls/internal/lsp/analysis/unusedparams/unusedparams.go152
1 files changed, 152 insertions, 0 deletions
diff --git a/gopls/internal/lsp/analysis/unusedparams/unusedparams.go b/gopls/internal/lsp/analysis/unusedparams/unusedparams.go
new file mode 100644
index 000000000..4c933c8fb
--- /dev/null
+++ b/gopls/internal/lsp/analysis/unusedparams/unusedparams.go
@@ -0,0 +1,152 @@
+// 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 defines an analyzer that checks for unused
+// parameters of functions.
+package unusedparams
+
+import (
+ "fmt"
+ "go/ast"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+const Doc = `check for unused parameters of functions
+
+The unusedparams analyzer checks functions to see if there are
+any parameters that are not being used.
+
+To reduce false positives it ignores:
+- methods
+- parameters that do not have a name or are underscored
+- functions in test files
+- functions with empty bodies or those with just a return stmt`
+
+var Analyzer = &analysis.Analyzer{
+ Name: "unusedparams",
+ Doc: Doc,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: run,
+}
+
+type paramData struct {
+ field *ast.Field
+ ident *ast.Ident
+ typObj types.Object
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ nodeFilter := []ast.Node{
+ (*ast.FuncDecl)(nil),
+ (*ast.FuncLit)(nil),
+ }
+
+ inspect.Preorder(nodeFilter, func(n ast.Node) {
+ var fieldList *ast.FieldList
+ var body *ast.BlockStmt
+
+ // Get the fieldList and body from the function node.
+ switch f := n.(type) {
+ case *ast.FuncDecl:
+ fieldList, body = f.Type.Params, f.Body
+ // TODO(golang/go#36602): add better handling for methods, if we enable methods
+ // we will get false positives if a struct is potentially implementing
+ // an interface.
+ if f.Recv != nil {
+ return
+ }
+ // Ignore functions in _test.go files to reduce false positives.
+ if file := pass.Fset.File(n.Pos()); file != nil && strings.HasSuffix(file.Name(), "_test.go") {
+ return
+ }
+ case *ast.FuncLit:
+ fieldList, body = f.Type.Params, f.Body
+ }
+ // If there are no arguments or the function is empty, then return.
+ if fieldList.NumFields() == 0 || body == nil || len(body.List) == 0 {
+ return
+ }
+
+ switch expr := body.List[0].(type) {
+ case *ast.ReturnStmt:
+ // Ignore functions that only contain a return statement to reduce false positives.
+ return
+ case *ast.ExprStmt:
+ callExpr, ok := expr.X.(*ast.CallExpr)
+ if !ok || len(body.List) > 1 {
+ break
+ }
+ // Ignore functions that only contain a panic statement to reduce false positives.
+ if fun, ok := callExpr.Fun.(*ast.Ident); ok && fun.Name == "panic" {
+ return
+ }
+ }
+
+ // Get the useful data from each field.
+ params := make(map[string]*paramData)
+ unused := make(map[*paramData]bool)
+ for _, f := range fieldList.List {
+ for _, i := range f.Names {
+ if i.Name == "_" {
+ continue
+ }
+ params[i.Name] = &paramData{
+ field: f,
+ ident: i,
+ typObj: pass.TypesInfo.ObjectOf(i),
+ }
+ unused[params[i.Name]] = true
+ }
+ }
+
+ // Traverse through the body of the function and
+ // check to see which parameters are unused.
+ ast.Inspect(body, func(node ast.Node) bool {
+ n, ok := node.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ param, ok := params[n.Name]
+ if !ok {
+ return false
+ }
+ if nObj := pass.TypesInfo.ObjectOf(n); nObj != param.typObj {
+ return false
+ }
+ delete(unused, param)
+ return false
+ })
+
+ // Create the reports for the unused parameters.
+ for u := range unused {
+ start, end := u.field.Pos(), u.field.End()
+ if len(u.field.Names) > 1 {
+ start, end = u.ident.Pos(), u.ident.End()
+ }
+ // TODO(golang/go#36602): Add suggested fixes to automatically
+ // remove the unused parameter from every use of this
+ // function.
+ pass.Report(analysis.Diagnostic{
+ Pos: start,
+ End: end,
+ Message: fmt.Sprintf("potentially unused parameter: '%s'", u.ident.Name),
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: `Replace with "_"`,
+ TextEdits: []analysis.TextEdit{{
+ Pos: u.ident.Pos(),
+ End: u.ident.End(),
+ NewText: []byte("_"),
+ }},
+ }},
+ })
+ }
+ })
+ return nil, nil
+}