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.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout")
63 Analyzer.Flags.Var(&funcs, "funcs",
64 "comma-separated list of functions whose results must be used")
65
66 stringMethods.Set("Error,String")
67 Analyzer.Flags.Var(&stringMethods, "stringmethods",
68 "comma-separated list of names of methods of type func() string whose results must be used")
69 }
70
71 func run(pass *analysis.Pass) (interface{}, error) {
72 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
73
74
75 pkgFuncs := make(map[[2]string]bool, len(funcs))
76 for s := range funcs {
77 if i := strings.LastIndexByte(s, '.'); i > 0 {
78 pkgFuncs[[2]string{s[:i], s[i+1:]}] = true
79 }
80 }
81
82 nodeFilter := []ast.Node{
83 (*ast.ExprStmt)(nil),
84 }
85 inspect.Preorder(nodeFilter, func(n ast.Node) {
86 call, ok := astutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
87 if !ok {
88 return
89 }
90
91
92 fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
93 if !ok {
94 return
95 }
96 if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
97
98 if types.Identical(sig, sigNoArgsStringResult) {
99 if stringMethods[fn.Name()] {
100 pass.Reportf(call.Lparen, "result of (%s).%s call not used",
101 sig.Recv().Type(), fn.Name())
102 }
103 }
104 } else {
105
106 if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
107 pass.Reportf(call.Lparen, "result of %s.%s call not used",
108 fn.Pkg().Path(), fn.Name())
109 }
110 }
111 })
112 return nil, nil
113 }
114
115
116 var sigNoArgsStringResult = types.NewSignature(nil, nil,
117 types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])),
118 false)
119
120 type stringSetFlag map[string]bool
121
122 func (ss *stringSetFlag) String() string {
123 var items []string
124 for item := range *ss {
125 items = append(items, item)
126 }
127 sort.Strings(items)
128 return strings.Join(items, ",")
129 }
130
131 func (ss *stringSetFlag) Set(s string) error {
132 m := make(map[string]bool)
133 if s != "" {
134 for _, name := range strings.Split(s, ",") {
135 if name == "" {
136 continue
137 }
138 m[name] = true
139 }
140 }
141 *ss = m
142 return nil
143 }
144
View as plain text