1
2
3
4
5
6
7 package unusedresult
8
9
10
11
12
13
14
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 "golang.org/x/tools/internal/analysisinternal"
30 )
31
32
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
44 var funcs, stringMethods stringSetFlag
45
46 func init() {
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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.Append": true,
69 "fmt.Appendf": true,
70 "fmt.Appendln": true,
71 "fmt.Errorf": true,
72 "fmt.Sprint": true,
73 "fmt.Sprintf": true,
74 "fmt.Sprintln": true,
75 "maps.All": true,
76 "maps.Clone": true,
77 "maps.Collect": true,
78 "maps.Equal": true,
79 "maps.EqualFunc": true,
80 "maps.Keys": true,
81 "maps.Values": true,
82 "slices.All": true,
83 "slices.AppendSeq": true,
84 "slices.Backward": true,
85 "slices.BinarySearch": true,
86 "slices.BinarySearchFunc": true,
87 "slices.Chunk": true,
88 "slices.Clip": true,
89 "slices.Clone": true,
90 "slices.Collect": true,
91 "slices.Compact": true,
92 "slices.CompactFunc": true,
93 "slices.Compare": true,
94 "slices.CompareFunc": true,
95 "slices.Concat": true,
96 "slices.Contains": true,
97 "slices.ContainsFunc": true,
98 "slices.Delete": true,
99 "slices.DeleteFunc": true,
100 "slices.Equal": true,
101 "slices.EqualFunc": true,
102 "slices.Grow": true,
103 "slices.Index": true,
104 "slices.IndexFunc": true,
105 "slices.Insert": true,
106 "slices.IsSorted": true,
107 "slices.IsSortedFunc": true,
108 "slices.Max": true,
109 "slices.MaxFunc": true,
110 "slices.Min": true,
111 "slices.MinFunc": true,
112 "slices.Repeat": true,
113 "slices.Replace": true,
114 "slices.Sorted": true,
115 "slices.SortedFunc": true,
116 "slices.SortedStableFunc": true,
117 "slices.Values": true,
118 "sort.Reverse": true,
119 }
120 Analyzer.Flags.Var(&funcs, "funcs",
121 "comma-separated list of functions whose results must be used")
122
123 stringMethods.Set("Error,String")
124 Analyzer.Flags.Var(&stringMethods, "stringmethods",
125 "comma-separated list of names of methods of type func() string whose results must be used")
126 }
127
128 func run(pass *analysis.Pass) (any, error) {
129 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
130
131
132 pkgFuncs := make(map[[2]string]bool, len(funcs))
133 for s := range funcs {
134 if i := strings.LastIndexByte(s, '.'); i > 0 {
135 pkgFuncs[[2]string{s[:i], s[i+1:]}] = true
136 }
137 }
138
139 nodeFilter := []ast.Node{
140 (*ast.ExprStmt)(nil),
141 }
142 inspect.Preorder(nodeFilter, func(n ast.Node) {
143 call, ok := ast.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
144 if !ok {
145 return
146 }
147
148
149 fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
150 if !ok {
151 return
152 }
153 if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
154
155 if types.Identical(sig, sigNoArgsStringResult) {
156 if stringMethods[fn.Name()] {
157 pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
158 "result of (%s).%s call not used",
159 sig.Recv().Type(), fn.Name())
160 }
161 }
162 } else {
163
164 if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
165 pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
166 "result of %s.%s call not used",
167 fn.Pkg().Path(), fn.Name())
168 }
169 }
170 })
171 return nil, nil
172 }
173
174
175 var sigNoArgsStringResult = types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])), false)
176
177 type stringSetFlag map[string]bool
178
179 func (ss *stringSetFlag) String() string {
180 var items []string
181 for item := range *ss {
182 items = append(items, item)
183 }
184 sort.Strings(items)
185 return strings.Join(items, ",")
186 }
187
188 func (ss *stringSetFlag) Set(s string) error {
189 m := make(map[string]bool)
190 if s != "" {
191 for _, name := range strings.Split(s, ",") {
192 if name == "" {
193 continue
194 }
195 m[name] = true
196 }
197 }
198 *ss = m
199 return nil
200 }
201
View as plain text