1
2
3
4
5
6
7
8 package analysisflags
9
10 import (
11 "crypto/sha256"
12 "encoding/gob"
13 "encoding/json"
14 "flag"
15 "fmt"
16 "io"
17 "log"
18 "os"
19 "strconv"
20
21 "golang.org/x/tools/go/analysis"
22 )
23
24
25 var (
26 JSON = false
27 Context = -1
28 Fix bool
29 Diff bool
30 )
31
32
33
34
35
36
37
38
39
40
41
42
43
44 func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
45
46 enabled := make(map[*analysis.Analyzer]*triState)
47 for _, a := range analyzers {
48 var prefix string
49
50
51 if multi {
52 prefix = a.Name + "."
53
54 enable := new(triState)
55 enableUsage := "enable " + a.Name + " analysis"
56 flag.Var(enable, a.Name, enableUsage)
57 enabled[a] = enable
58 }
59
60 a.Flags.VisitAll(func(f *flag.Flag) {
61 if !multi && flag.Lookup(f.Name) != nil {
62 log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
63 return
64 }
65
66 name := prefix + f.Name
67 flag.Var(f.Value, name, f.Usage)
68 })
69 }
70
71
72 printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
73 addVersionFlag()
74
75
76 flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
77 flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
78 flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes")
79 flag.BoolVar(&Diff, "diff", false, "with -fix, don't update the files, but print a unified diff")
80
81
82
83 _ = flag.Bool("source", false, "no effect (deprecated)")
84 _ = flag.Bool("v", false, "no effect (deprecated)")
85 _ = flag.Bool("all", false, "no effect (deprecated)")
86 _ = flag.String("tags", "", "no effect (deprecated)")
87 for old, new := range vetLegacyFlags {
88 newFlag := flag.Lookup(new)
89 if newFlag != nil && flag.Lookup(old) == nil {
90 flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
91 }
92 }
93
94 flag.Parse()
95
96
97 if *printflags {
98 printFlags()
99 os.Exit(0)
100 }
101
102 everything := expand(analyzers)
103
104
105
106 if multi {
107 var hasTrue, hasFalse bool
108 for _, ts := range enabled {
109 switch *ts {
110 case setTrue:
111 hasTrue = true
112 case setFalse:
113 hasFalse = true
114 }
115 }
116
117 var keep []*analysis.Analyzer
118 if hasTrue {
119 for _, a := range analyzers {
120 if *enabled[a] == setTrue {
121 keep = append(keep, a)
122 }
123 }
124 analyzers = keep
125 } else if hasFalse {
126 for _, a := range analyzers {
127 if *enabled[a] != setFalse {
128 keep = append(keep, a)
129 }
130 }
131 analyzers = keep
132 }
133 }
134
135
136
137 kept := expand(analyzers)
138 for a := range everything {
139 if !kept[a] {
140 for _, f := range a.FactTypes {
141 gob.Register(f)
142 }
143 }
144 }
145
146 return analyzers
147 }
148
149 func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
150 seen := make(map[*analysis.Analyzer]bool)
151 var visitAll func([]*analysis.Analyzer)
152 visitAll = func(analyzers []*analysis.Analyzer) {
153 for _, a := range analyzers {
154 if !seen[a] {
155 seen[a] = true
156 visitAll(a.Requires)
157 }
158 }
159 }
160 visitAll(analyzers)
161 return seen
162 }
163
164 func printFlags() {
165 type jsonFlag struct {
166 Name string
167 Bool bool
168 Usage string
169 }
170 var flags []jsonFlag = nil
171 flag.VisitAll(func(f *flag.Flag) {
172
173
174
175 switch f.Name {
176 case "debug", "cpuprofile", "memprofile", "trace", "fix":
177 return
178 }
179
180 b, ok := f.Value.(interface{ IsBoolFlag() bool })
181 isBool := ok && b.IsBoolFlag()
182 flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
183 })
184 data, err := json.MarshalIndent(flags, "", "\t")
185 if err != nil {
186 log.Fatal(err)
187 }
188 os.Stdout.Write(data)
189 }
190
191
192
193
194
195
196
197 func addVersionFlag() {
198 if flag.Lookup("V") == nil {
199 flag.Var(versionFlag{}, "V", "print version and exit")
200 }
201 }
202
203
204 type versionFlag struct{}
205
206 func (versionFlag) IsBoolFlag() bool { return true }
207 func (versionFlag) Get() any { return nil }
208 func (versionFlag) String() string { return "" }
209 func (versionFlag) Set(s string) error {
210 if s != "full" {
211 log.Fatalf("unsupported flag value: -V=%s (use -V=full)", s)
212 }
213
214
215
216
217
218
219
220
221
222
223 progname, err := os.Executable()
224 if err != nil {
225 return err
226 }
227 f, err := os.Open(progname)
228 if err != nil {
229 log.Fatal(err)
230 }
231 h := sha256.New()
232 if _, err := io.Copy(h, f); err != nil {
233 log.Fatal(err)
234 }
235 f.Close()
236 fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
237 progname, string(h.Sum(nil)))
238 os.Exit(0)
239 return nil
240 }
241
242
243
244
245
246
247
248 type triState int
249
250 const (
251 unset triState = iota
252 setTrue
253 setFalse
254 )
255
256
257
258 func (ts *triState) Get() any {
259 return *ts == setTrue
260 }
261
262 func (ts *triState) Set(value string) error {
263 b, err := strconv.ParseBool(value)
264 if err != nil {
265
266
267 return fmt.Errorf("want true or false")
268 }
269 if b {
270 *ts = setTrue
271 } else {
272 *ts = setFalse
273 }
274 return nil
275 }
276
277 func (ts *triState) String() string {
278 switch *ts {
279 case unset:
280 return "true"
281 case setTrue:
282 return "true"
283 case setFalse:
284 return "false"
285 }
286 panic("not reached")
287 }
288
289 func (ts triState) IsBoolFlag() bool {
290 return true
291 }
292
293
294
295
296
297 var vetLegacyFlags = map[string]string{
298
299 "bool": "bools",
300 "buildtags": "buildtag",
301 "methods": "stdmethods",
302 "rangeloops": "loopclosure",
303
304
305 "compositewhitelist": "composites.whitelist",
306 "printfuncs": "printf.funcs",
307 "shadowstrict": "shadow.strict",
308 "unusedfuncs": "unusedresult.funcs",
309 "unusedstringmethods": "unusedresult.stringmethods",
310 }
311
View as plain text