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 }