1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package unusedparams defines an analyzer that checks for unused
6// parameters of functions.
7package unusedparams
8
9import (
10	"fmt"
11	"go/ast"
12	"go/types"
13	"strings"
14
15	"golang.org/x/tools/go/analysis"
16	"golang.org/x/tools/go/analysis/passes/inspect"
17	"golang.org/x/tools/go/ast/inspector"
18)
19
20const Doc = `check for unused parameters of functions
21
22The unusedparams analyzer checks functions to see if there are
23any parameters that are not being used.
24
25To reduce false positives it ignores:
26- methods
27- parameters that do not have a name or are underscored
28- functions in test files
29- functions with empty bodies or those with just a return stmt`
30
31var Analyzer = &analysis.Analyzer{
32	Name:     "unusedparams",
33	Doc:      Doc,
34	Requires: []*analysis.Analyzer{inspect.Analyzer},
35	Run:      run,
36}
37
38type paramData struct {
39	field  *ast.Field
40	ident  *ast.Ident
41	typObj types.Object
42}
43
44func run(pass *analysis.Pass) (interface{}, error) {
45	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
46	nodeFilter := []ast.Node{
47		(*ast.FuncDecl)(nil),
48		(*ast.FuncLit)(nil),
49	}
50
51	inspect.Preorder(nodeFilter, func(n ast.Node) {
52		var fieldList *ast.FieldList
53		var body *ast.BlockStmt
54
55		// Get the fieldList and body from the function node.
56		switch f := n.(type) {
57		case *ast.FuncDecl:
58			fieldList, body = f.Type.Params, f.Body
59			// TODO(golang/go#36602): add better handling for methods, if we enable methods
60			// we will get false positives if a struct is potentially implementing
61			// an interface.
62			if f.Recv != nil {
63				return
64			}
65			// Ignore functions in _test.go files to reduce false positives.
66			if file := pass.Fset.File(n.Pos()); file != nil && strings.HasSuffix(file.Name(), "_test.go") {
67				return
68			}
69		case *ast.FuncLit:
70			fieldList, body = f.Type.Params, f.Body
71		}
72		// If there are no arguments or the function is empty, then return.
73		if fieldList.NumFields() == 0 || body == nil || len(body.List) == 0 {
74			return
75		}
76
77		switch expr := body.List[0].(type) {
78		case *ast.ReturnStmt:
79			// Ignore functions that only contain a return statement to reduce false positives.
80			return
81		case *ast.ExprStmt:
82			callExpr, ok := expr.X.(*ast.CallExpr)
83			if !ok || len(body.List) > 1 {
84				break
85			}
86			// Ignore functions that only contain a panic statement to reduce false positives.
87			if fun, ok := callExpr.Fun.(*ast.Ident); ok && fun.Name == "panic" {
88				return
89			}
90		}
91
92		// Get the useful data from each field.
93		params := make(map[string]*paramData)
94		unused := make(map[*paramData]bool)
95		for _, f := range fieldList.List {
96			for _, i := range f.Names {
97				if i.Name == "_" {
98					continue
99				}
100				params[i.Name] = &paramData{
101					field:  f,
102					ident:  i,
103					typObj: pass.TypesInfo.ObjectOf(i),
104				}
105				unused[params[i.Name]] = true
106			}
107		}
108
109		// Traverse through the body of the function and
110		// check to see which parameters are unused.
111		ast.Inspect(body, func(node ast.Node) bool {
112			n, ok := node.(*ast.Ident)
113			if !ok {
114				return true
115			}
116			param, ok := params[n.Name]
117			if !ok {
118				return false
119			}
120			if nObj := pass.TypesInfo.ObjectOf(n); nObj != param.typObj {
121				return false
122			}
123			delete(unused, param)
124			return false
125		})
126
127		// Create the reports for the unused parameters.
128		for u := range unused {
129			start, end := u.field.Pos(), u.field.End()
130			if len(u.field.Names) > 1 {
131				start, end = u.ident.Pos(), u.ident.End()
132			}
133			// TODO(golang/go#36602): Add suggested fixes to automatically
134			// remove the unused parameter. To start, just remove it from the
135			// function declaration. Later, remove it from every use of this
136			// function.
137			pass.Report(analysis.Diagnostic{
138				Pos:     start,
139				End:     end,
140				Message: fmt.Sprintf("potentially unused parameter: '%s'", u.ident.Name),
141			})
142		}
143	})
144	return nil, nil
145}
146