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 posn := fset.Position(diag.Pos)
322 fmt.Fprintf(out, "%s: %s\n", posn, diag.Message)
323
324
325 if contextLines >= 0 {
326 posn := fset.Position(diag.Pos)
327 end := fset.Position(diag.End)
328 if !end.IsValid() {
329 end = posn
330 }
331 data, _ := os.ReadFile(posn.Filename)
332 lines := strings.Split(string(data), "\n")
333 for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
334 if 1 <= i && i <= len(lines) {
335 fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
336 }
337 }
338 }
339 }
340
341
342
343 type JSONTree map[string]map[string]any
344
345
346
347
348 type JSONTextEdit struct {
349 Filename string `json:"filename"`
350 Start int `json:"start"`
351 End int `json:"end"`
352 New string `json:"new"`
353 }
354
355
356
357
358 type JSONSuggestedFix struct {
359 Message string `json:"message"`
360 Edits []JSONTextEdit `json:"edits"`
361 }
362
363
364
365
366 type JSONDiagnostic struct {
367 Category string `json:"category,omitempty"`
368 Posn string `json:"posn"`
369 Message string `json:"message"`
370 SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
371 Related []JSONRelatedInformation `json:"related,omitempty"`
372 }
373
374
375
376
377
378 type JSONRelatedInformation struct {
379 Posn string `json:"posn"`
380 Message string `json:"message"`
381 }
382
383
384
385 func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
386 var v any
387 if err != nil {
388 type jsonError struct {
389 Err string `json:"error"`
390 }
391 v = jsonError{err.Error()}
392 } else if len(diags) > 0 {
393 diagnostics := make([]JSONDiagnostic, 0, len(diags))
394 for _, f := range diags {
395 var fixes []JSONSuggestedFix
396 for _, fix := range f.SuggestedFixes {
397 var edits []JSONTextEdit
398 for _, edit := range fix.TextEdits {
399 edits = append(edits, JSONTextEdit{
400 Filename: fset.Position(edit.Pos).Filename,
401 Start: fset.Position(edit.Pos).Offset,
402 End: fset.Position(edit.End).Offset,
403 New: string(edit.NewText),
404 })
405 }
406 fixes = append(fixes, JSONSuggestedFix{
407 Message: fix.Message,
408 Edits: edits,
409 })
410 }
411 var related []JSONRelatedInformation
412 for _, r := range f.Related {
413 related = append(related, JSONRelatedInformation{
414 Posn: fset.Position(r.Pos).String(),
415 Message: r.Message,
416 })
417 }
418 jdiag := JSONDiagnostic{
419 Category: f.Category,
420 Posn: fset.Position(f.Pos).String(),
421 Message: f.Message,
422 SuggestedFixes: fixes,
423 Related: related,
424 }
425 diagnostics = append(diagnostics, jdiag)
426 }
427 v = diagnostics
428 }
429 if v != nil {
430 m, ok := tree[id]
431 if !ok {
432 m = make(map[string]any)
433 tree[id] = m
434 }
435 m[name] = v
436 }
437 }
438
439 func (tree JSONTree) Print(out io.Writer) error {
440 data, err := json.MarshalIndent(tree, "", "\t")
441 if err != nil {
442 log.Panicf("internal error: JSON marshaling failed: %v", err)
443 }
444 _, err = fmt.Fprintf(out, "%s\n", data)
445 return err
446 }
447
View as plain text