// 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 || 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. To start, just remove it from the // function declaration. Later, remove it from every use of this // function. pass.Report(analysis.Diagnostic{ Pos: start, End: end, Message: fmt.Sprintf("potentially unused parameter: '%s'", u.ident.Name), }) } }) return nil, nil }