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 )
30
31
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
43 var funcs, stringMethods stringSetFlag
44
45 func init() {
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
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
106 }
107
108
109 fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
110 if !ok {
111 return
112 }
113 if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
114
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
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
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)
150 if s != "" {
151 for _, name := range strings.Split(s, ",") {
152 if name == "" {
153 continue
154 }
155 m[name] = true
156 }
157 }
158 *ss = m
159 return nil
160 }
161
View as plain text