|  | // 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] = ¶mData{ | 
|  | 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 | 
|  | } |