Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go

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

View as plain text