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() interface{} { 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 func triStateFlag(name string, value triState, usage string) *triState {
254 flag.Var(&value, name, usage)
255 return &value
256 }
257
258
259
260 func (ts *triState) Get() interface{} {
261 return *ts == setTrue
262 }
263
264 func (ts triState) isTrue() bool {
265 return ts == setTrue
266 }
267
268 func (ts *triState) Set(value string) error {
269 b, err := strconv.ParseBool(value)
270 if err != nil {
271
272
273 return fmt.Errorf("want true or false")
274 }
275 if b {
276 *ts = setTrue
277 } else {
278 *ts = setFalse
279 }
280 return nil
281 }
282
283 func (ts *triState) String() string {
284 switch *ts {
285 case unset:
286 return "true"
287 case setTrue:
288 return "true"
289 case setFalse:
290 return "false"
291 }
292 panic("not reached")
293 }
294
295 func (ts triState) IsBoolFlag() bool {
296 return true
297 }
298
299
300
301
302
303 var vetLegacyFlags = map[string]string{
304
305 "bool": "bools",
306 "buildtags": "buildtag",
307 "methods": "stdmethods",
308 "rangeloops": "loopclosure",
309
310
311 "compositewhitelist": "composites.whitelist",
312 "printfuncs": "printf.funcs",
313 "shadowstrict": "shadow.strict",
314 "unusedfuncs": "unusedresult.funcs",
315 "unusedstringmethods": "unusedresult.stringmethods",
316 }
317
318
319
320
321
322 func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
323 posn := fset.Position(diag.Pos)
324 fmt.Fprintf(os.Stderr, "%s: %s\n", posn, diag.Message)
325
326
327 if Context >= 0 {
328 posn := fset.Position(diag.Pos)
329 end := fset.Position(diag.End)
330 if !end.IsValid() {
331 end = posn
332 }
333 data, _ := os.ReadFile(posn.Filename)
334 lines := strings.Split(string(data), "\n")
335 for i := posn.Line - Context; i <= end.Line+Context; i++ {
336 if 1 <= i && i <= len(lines) {
337 fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
338 }
339 }
340 }
341 }
342
343
344
345 type JSONTree map[string]map[string]interface{}
346
347
348
349
350 type JSONTextEdit struct {
351 Filename string `json:"filename"`
352 Start int `json:"start"`
353 End int `json:"end"`
354 New string `json:"new"`
355 }
356
357
358
359
360 type JSONSuggestedFix struct {
361 Message string `json:"message"`
362 Edits []JSONTextEdit `json:"edits"`
363 }
364
365
366
367
368 type JSONDiagnostic struct {
369 Category string `json:"category,omitempty"`
370 Posn string `json:"posn"`
371 Message string `json:"message"`
372 SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
373 Related []JSONRelatedInformation `json:"related,omitempty"`
374 }
375
376
377
378
379
380 type JSONRelatedInformation struct {
381 Posn string `json:"posn"`
382 Message string `json:"message"`
383 }
384
385
386
387 func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
388 var v interface{}
389 if err != nil {
390 type jsonError struct {
391 Err string `json:"error"`
392 }
393 v = jsonError{err.Error()}
394 } else if len(diags) > 0 {
395 diagnostics := make([]JSONDiagnostic, 0, len(diags))
396 for _, f := range diags {
397 var fixes []JSONSuggestedFix
398 for _, fix := range f.SuggestedFixes {
399 var edits []JSONTextEdit
400 for _, edit := range fix.TextEdits {
401 edits = append(edits, JSONTextEdit{
402 Filename: fset.Position(edit.Pos).Filename,
403 Start: fset.Position(edit.Pos).Offset,
404 End: fset.Position(edit.End).Offset,
405 New: string(edit.NewText),
406 })
407 }
408 fixes = append(fixes, JSONSuggestedFix{
409 Message: fix.Message,
410 Edits: edits,
411 })
412 }
413 var related []JSONRelatedInformation
414 for _, r := range f.Related {
415 related = append(related, JSONRelatedInformation{
416 Posn: fset.Position(r.Pos).String(),
417 Message: r.Message,
418 })
419 }
420 jdiag := JSONDiagnostic{
421 Category: f.Category,
422 Posn: fset.Position(f.Pos).String(),
423 Message: f.Message,
424 SuggestedFixes: fixes,
425 Related: related,
426 }
427 diagnostics = append(diagnostics, jdiag)
428 }
429 v = diagnostics
430 }
431 if v != nil {
432 m, ok := tree[id]
433 if !ok {
434 m = make(map[string]interface{})
435 tree[id] = m
436 }
437 m[name] = v
438 }
439 }
440
441 func (tree JSONTree) Print() {
442 data, err := json.MarshalIndent(tree, "", "\t")
443 if err != nil {
444 log.Panicf("internal error: JSON marshaling failed: %v", err)
445 }
446 fmt.Printf("%s\n", data)
447 }
448
View as plain text