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/astutil"
28 "golang.org/x/tools/go/ast/inspector"
29 "golang.org/x/tools/go/types/typeutil"
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.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
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
107 }
108
109
110 fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
111 if !ok {
112 return
113 }
114 if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
115
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
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
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)
151 if s != "" {
152 for _, name := range strings.Split(s, ",") {
153 if name == "" {
154 continue
155 }
156 m[name] = true
157 }
158 }
159 *ss = m
160 return nil
161 }
162
View as plain text