1
2
3
4
5
6
7 package analysisflags
8
9 import (
10 "crypto/sha256"
11 "encoding/gob"
12 "encoding/json"
13 "flag"
14 "fmt"
15 "go/token"
16 "io"
17 "log"
18 "os"
19 "strconv"
20 "strings"
21
22 "golang.org/x/tools/go/analysis"
23 )
24
25
26 var (
27 JSON = false
28 Context = -1
29 )
30
31
32
33
34
35
36
37
38
39
40
41
42
43 func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
44
45 enabled := make(map[*analysis.Analyzer]*triState)
46 for _, a := range analyzers {
47 var prefix string
48
49
50 if multi {
51 prefix = a.Name + "."
52
53 enable := new(triState)
54 enableUsage := "enable " + a.Name + " analysis"
55 flag.Var(enable, a.Name, enableUsage)
56 enabled[a] = enable
57 }
58
59 a.Flags.VisitAll(func(f *flag.Flag) {
60 if !multi && flag.Lookup(f.Name) != nil {
61 log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
62 return
63 }
64
65 name := prefix + f.Name
66 flag.Var(f.Value, name, f.Usage)
67 })
68 }
69
70
71 printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
72 addVersionFlag()
73
74
75 flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
76 flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
77
78
79
80 _ = flag.Bool("source", false, "no effect (deprecated)")
81 _ = flag.Bool("v", false, "no effect (deprecated)")
82 _ = flag.Bool("all", false, "no effect (deprecated)")
83 _ = flag.String("tags", "", "no effect (deprecated)")
84 for old, new := range vetLegacyFlags {
85 newFlag := flag.Lookup(new)
86 if newFlag != nil && flag.Lookup(old) == nil {
87 flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
88 }
89 }
90
91 flag.Parse()
92
93
94 if *printflags {
95 printFlags()
96 os.Exit(0)
97 }
98
99 everything := expand(analyzers)
100
101
102
103 if multi {
104 var hasTrue, hasFalse bool
105 for _, ts := range enabled {
106 switch *ts {
107 case setTrue:
108 hasTrue = true
109 case setFalse:
110 hasFalse = true
111 }
112 }
113
114 var keep []*analysis.Analyzer
115 if hasTrue {
116 for _, a := range analyzers {
117 if *enabled[a] == setTrue {
118 keep = append(keep, a)
119 }
120 }
121 analyzers = keep
122 } else if hasFalse {
123 for _, a := range analyzers {
124 if *enabled[a] != setFalse {
125 keep = append(keep, a)
126 }
127 }
128 analyzers = keep
129 }
130 }
131
132
133
134 kept := expand(analyzers)
135 for a := range everything {
136 if !kept[a] {
137 for _, f := range a.FactTypes {
138 gob.Register(f)
139 }
140 }
141 }
142
143 return analyzers
144 }
145
146 func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
147 seen := make(map[*analysis.Analyzer]bool)
148 var visitAll func([]*analysis.Analyzer)
149 visitAll = func(analyzers []*analysis.Analyzer) {
150 for _, a := range analyzers {
151 if !seen[a] {
152 seen[a] = true
153 visitAll(a.Requires)
154 }
155 }
156 }
157 visitAll(analyzers)
158 return seen
159 }
160
161 func printFlags() {
162 type jsonFlag struct {
163 Name string
164 Bool bool
165 Usage string
166 }
167 var flags []jsonFlag = nil
168 flag.VisitAll(func(f *flag.Flag) {
169
170
171
172 switch f.Name {
173 case "debug", "cpuprofile", "memprofile", "trace", "fix":
174 return
175 }
176
177 b, ok := f.Value.(interface{ IsBoolFlag() bool })
178 isBool := ok && b.IsBoolFlag()
179 flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
180 })
181 data, err := json.MarshalIndent(flags, "", "\t")
182 if err != nil {
183 log.Fatal(err)
184 }
185 os.Stdout.Write(data)
186 }
187
188
189
190
191
192
193
194 func addVersionFlag() {
195 if flag.Lookup("V") == nil {
196 flag.Var(versionFlag{}, "V", "print version and exit")
197 }
198 }
199
200
201 type versionFlag struct{}
202
203 func (versionFlag) IsBoolFlag() bool { return true }
204 func (versionFlag) Get() any { return nil }
205 func (versionFlag) String() string { return "" }
206 func (versionFlag) Set(s string) error {
207 if s != "full" {
208 log.Fatalf("unsupported flag value: -V=%s (use -V=full)", s)
209 }
210
211
212
213
214
215
216
217
218
219
220 progname, err := os.Executable()
221 if err != nil {
222 return err
223 }
224 f, err := os.Open(progname)
225 if err != nil {
226 log.Fatal(err)
227 }
228 h := sha256.New()
229 if _, err := io.Copy(h, f); err != nil {
230 log.Fatal(err)
231 }
232 f.Close()
233 fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
234 progname, string(h.Sum(nil)))
235 os.Exit(0)
236 return nil
237 }
238
239
240
241
242
243
244
245 type triState int
246
247 const (
248 unset triState = iota
249 setTrue
250 setFalse
251 )
252
253
254
255 func (ts *triState) Get() any {
256 return *ts == setTrue
257 }
258
259 func (ts *triState) Set(value string) error {
260 b, err := strconv.ParseBool(value)
261 if err != nil {
262
263
264 return fmt.Errorf("want true or false")
265 }
266 if b {
267 *ts = setTrue
268 } else {
269 *ts = setFalse
270 }
271 return nil
272 }
273
274 func (ts *triState) String() string {
275 switch *ts {
276 case unset:
277 return "true"
278 case setTrue:
279 return "true"
280 case setFalse:
281 return "false"
282 }
283 panic("not reached")
284 }
285
286 func (ts triState) IsBoolFlag() bool {
287 return true
288 }
289
290
291
292
293
294 var vetLegacyFlags = map[string]string{
295
296 "bool": "bools",
297 "buildtags": "buildtag",
298 "methods": "stdmethods",
299 "rangeloops": "loopclosure",
300
301
302 "compositewhitelist": "composites.whitelist",
303 "printfuncs": "printf.funcs",
304 "shadowstrict": "shadow.strict",
305 "unusedfuncs": "unusedresult.funcs",
306 "unusedstringmethods": "unusedresult.stringmethods",
307 }
308
309
310
311
312
313
314
315
316
317
318
319
320 func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
321 print := func(pos, end token.Pos, message string) {
322 posn := fset.Position(pos)
323 fmt.Fprintf(out, "%s: %s\n", posn, message)
324
325
326 if contextLines >= 0 {
327 end := fset.Position(end)
328 if !end.IsValid() {
329 end = posn
330 }
331
332
333 data, _ := os.ReadFile(posn.Filename)
334 lines := strings.Split(string(data), "\n")
335 for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
336 if 1 <= i && i <= len(lines) {
337 fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
338 }
339 }
340 }
341 }
342
343 print(diag.Pos, diag.End, diag.Message)
344 for _, rel := range diag.Related {
345 print(rel.Pos, rel.End, "\t"+rel.Message)
346 }
347 }
348
349
350
351 type JSONTree map[string]map[string]any
352
353
354
355
356 type JSONTextEdit struct {
357 Filename string `json:"filename"`
358 Start int `json:"start"`
359 End int `json:"end"`
360 New string `json:"new"`
361 }
362
363
364
365
366 type JSONSuggestedFix struct {
367 Message string `json:"message"`
368 Edits []JSONTextEdit `json:"edits"`
369 }
370
371
372
373
374 type JSONDiagnostic struct {
375 Category string `json:"category,omitempty"`
376 Posn string `json:"posn"`
377 Message string `json:"message"`
378 SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
379 Related []JSONRelatedInformation `json:"related,omitempty"`
380 }
381
382
383
384
385
386 type JSONRelatedInformation struct {
387 Posn string `json:"posn"`
388 Message string `json:"message"`
389 }
390
391
392
393 func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
394 var v any
395 if err != nil {
396 type jsonError struct {
397 Err string `json:"error"`
398 }
399 v = jsonError{err.Error()}
400 } else if len(diags) > 0 {
401 diagnostics := make([]JSONDiagnostic, 0, len(diags))
402 for _, f := range diags {
403 var fixes []JSONSuggestedFix
404 for _, fix := range f.SuggestedFixes {
405 var edits []JSONTextEdit
406 for _, edit := range fix.TextEdits {
407 edits = append(edits, JSONTextEdit{
408 Filename: fset.Position(edit.Pos).Filename,
409 Start: fset.Position(edit.Pos).Offset,
410 End: fset.Position(edit.End).Offset,
411 New: string(edit.NewText),
412 })
413 }
414 fixes = append(fixes, JSONSuggestedFix{
415 Message: fix.Message,
416 Edits: edits,
417 })
418 }
419 var related []JSONRelatedInformation
420 for _, r := range f.Related {
421 related = append(related, JSONRelatedInformation{
422 Posn: fset.Position(r.Pos).String(),
423 Message: r.Message,
424 })
425 }
426 jdiag := JSONDiagnostic{
427 Category: f.Category,
428 Posn: fset.Position(f.Pos).String(),
429 Message: f.Message,
430 SuggestedFixes: fixes,
431 Related: related,
432 }
433 diagnostics = append(diagnostics, jdiag)
434 }
435 v = diagnostics
436 }
437 if v != nil {
438 m, ok := tree[id]
439 if !ok {
440 m = make(map[string]any)
441 tree[id] = m
442 }
443 m[name] = v
444 }
445 }
446
447 func (tree JSONTree) Print(out io.Writer) error {
448 data, err := json.MarshalIndent(tree, "", "\t")
449 if err != nil {
450 log.Panicf("internal error: JSON marshaling failed: %v", err)
451 }
452 _, err = fmt.Fprintf(out, "%s\n", data)
453 return err
454 }
455
View as plain text