github.com/v2fly/tools@v0.100.0/internal/lsp/analysis/unusedparams/unusedparams.go (about)

     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.
     7  package unusedparams
     8  
     9  import (
    10  	"fmt"
    11  	"go/ast"
    12  	"go/types"
    13  	"strings"
    14  
    15  	"github.com/v2fly/tools/go/analysis"
    16  	"github.com/v2fly/tools/go/analysis/passes/inspect"
    17  	"github.com/v2fly/tools/go/ast/inspector"
    18  )
    19  
    20  const Doc = `check for unused parameters of functions
    21  
    22  The unusedparams analyzer checks functions to see if there are
    23  any parameters that are not being used.
    24  
    25  To 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  
    31  var Analyzer = &analysis.Analyzer{
    32  	Name:     "unusedparams",
    33  	Doc:      Doc,
    34  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    35  	Run:      run,
    36  }
    37  
    38  type paramData struct {
    39  	field  *ast.Field
    40  	ident  *ast.Ident
    41  	typObj types.Object
    42  }
    43  
    44  func 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  }