golang.org/x/tools@v0.21.0/go/analysis/passes/unusedresult/unusedresult.go (about)

     1  // Copyright 2015 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 unusedresult defines an analyzer that checks for unused
     6  // results of calls to certain functions.
     7  package unusedresult
     8  
     9  // It is tempting to make this analysis inductive: for each function
    10  // that tail-calls one of the functions that we check, check those
    11  // functions too. However, just because you must use the result of
    12  // fmt.Sprintf doesn't mean you need to use the result of every
    13  // function that returns a formatted string: it may have other results
    14  // and effects.
    15  
    16  import (
    17  	_ "embed"
    18  	"go/ast"
    19  	"go/token"
    20  	"go/types"
    21  	"sort"
    22  	"strings"
    23  
    24  	"golang.org/x/tools/go/analysis"
    25  	"golang.org/x/tools/go/analysis/passes/inspect"
    26  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    27  	"golang.org/x/tools/go/ast/astutil"
    28  	"golang.org/x/tools/go/ast/inspector"
    29  	"golang.org/x/tools/go/types/typeutil"
    30  )
    31  
    32  //go:embed doc.go
    33  var doc string
    34  
    35  var Analyzer = &analysis.Analyzer{
    36  	Name:     "unusedresult",
    37  	Doc:      analysisutil.MustExtractDoc(doc, "unusedresult"),
    38  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
    39  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    40  	Run:      run,
    41  }
    42  
    43  // flags
    44  var funcs, stringMethods stringSetFlag
    45  
    46  func init() {
    47  	// TODO(adonovan): provide a comment or declaration syntax to
    48  	// allow users to add their functions to this set using facts.
    49  	// For example:
    50  	//
    51  	//    func ignoringTheErrorWouldBeVeryBad() error {
    52  	//      type mustUseResult struct{} // enables vet unusedresult check
    53  	//      ...
    54  	//    }
    55  	//
    56  	//    ignoringTheErrorWouldBeVeryBad() // oops
    57  	//
    58  
    59  	// List standard library functions here.
    60  	// The context.With{Cancel,Deadline,Timeout} entries are
    61  	// effectively redundant wrt the lostcancel analyzer.
    62  	funcs = stringSetFlag{
    63  		"context.WithCancel":   true,
    64  		"context.WithDeadline": true,
    65  		"context.WithTimeout":  true,
    66  		"context.WithValue":    true,
    67  		"errors.New":           true,
    68  		"fmt.Errorf":           true,
    69  		"fmt.Sprint":           true,
    70  		"fmt.Sprintf":          true,
    71  		"slices.Clip":          true,
    72  		"slices.Compact":       true,
    73  		"slices.CompactFunc":   true,
    74  		"slices.Delete":        true,
    75  		"slices.DeleteFunc":    true,
    76  		"slices.Grow":          true,
    77  		"slices.Insert":        true,
    78  		"slices.Replace":       true,
    79  		"sort.Reverse":         true,
    80  	}
    81  	Analyzer.Flags.Var(&funcs, "funcs",
    82  		"comma-separated list of functions whose results must be used")
    83  
    84  	stringMethods.Set("Error,String")
    85  	Analyzer.Flags.Var(&stringMethods, "stringmethods",
    86  		"comma-separated list of names of methods of type func() string whose results must be used")
    87  }
    88  
    89  func run(pass *analysis.Pass) (interface{}, error) {
    90  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    91  
    92  	// Split functions into (pkg, name) pairs to save allocation later.
    93  	pkgFuncs := make(map[[2]string]bool, len(funcs))
    94  	for s := range funcs {
    95  		if i := strings.LastIndexByte(s, '.'); i > 0 {
    96  			pkgFuncs[[2]string{s[:i], s[i+1:]}] = true
    97  		}
    98  	}
    99  
   100  	nodeFilter := []ast.Node{
   101  		(*ast.ExprStmt)(nil),
   102  	}
   103  	inspect.Preorder(nodeFilter, func(n ast.Node) {
   104  		call, ok := astutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
   105  		if !ok {
   106  			return // not a call statement
   107  		}
   108  
   109  		// Call to function or method?
   110  		fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
   111  		if !ok {
   112  			return // e.g. var or builtin
   113  		}
   114  		if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
   115  			// method (e.g. foo.String())
   116  			if types.Identical(sig, sigNoArgsStringResult) {
   117  				if stringMethods[fn.Name()] {
   118  					pass.Reportf(call.Lparen, "result of (%s).%s call not used",
   119  						sig.Recv().Type(), fn.Name())
   120  				}
   121  			}
   122  		} else {
   123  			// package-level function (e.g. fmt.Errorf)
   124  			if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
   125  				pass.Reportf(call.Lparen, "result of %s.%s call not used",
   126  					fn.Pkg().Path(), fn.Name())
   127  			}
   128  		}
   129  	})
   130  	return nil, nil
   131  }
   132  
   133  // func() string
   134  var sigNoArgsStringResult = types.NewSignature(nil, nil,
   135  	types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])),
   136  	false)
   137  
   138  type stringSetFlag map[string]bool
   139  
   140  func (ss *stringSetFlag) String() string {
   141  	var items []string
   142  	for item := range *ss {
   143  		items = append(items, item)
   144  	}
   145  	sort.Strings(items)
   146  	return strings.Join(items, ",")
   147  }
   148  
   149  func (ss *stringSetFlag) Set(s string) error {
   150  	m := make(map[string]bool) // clobber previous value
   151  	if s != "" {
   152  		for _, name := range strings.Split(s, ",") {
   153  			if name == "" {
   154  				continue // TODO: report error? proceed?
   155  			}
   156  			m[name] = true
   157  		}
   158  	}
   159  	*ss = m
   160  	return nil
   161  }