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 "io/ioutil"
18 "log"
19 "os"
20 "strconv"
21 "strings"
22
23 "golang.org/x/tools/go/analysis"
24 )
25
26
27 var (
28 JSON = false
29 Context = -1
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
79
80
81 _ = flag.Bool("source", false, "no effect (deprecated)")
82 _ = flag.Bool("v", false, "no effect (deprecated)")
83 _ = flag.Bool("all", false, "no effect (deprecated)")
84 _ = flag.String("tags", "", "no effect (deprecated)")
85 for old, new := range vetLegacyFlags {
86 newFlag := flag.Lookup(new)
87 if newFlag != nil && flag.Lookup(old) == nil {
88 flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
89 }
90 }
91
92 flag.Parse()
93
94
95 if *printflags {
96 printFlags()
97 os.Exit(0)
98 }
99
100 everything := expand(analyzers)
101
102
103
104 if multi {
105 var hasTrue, hasFalse bool
106 for _, ts := range enabled {
107 switch *ts {
108 case setTrue:
109 hasTrue = true
110 case setFalse:
111 hasFalse = true
112 }
113 }
114
115 var keep []*analysis.Analyzer
116 if hasTrue {
117 for _, a := range analyzers {
118 if *enabled[a] == setTrue {
119 keep = append(keep, a)
120 }
121 }
122 analyzers = keep
123 } else if hasFalse {
124 for _, a := range analyzers {
125 if *enabled[a] != setFalse {
126 keep = append(keep, a)
127 }
128 }
129 analyzers = keep
130 }
131 }
132
133
134
135 kept := expand(analyzers)
136 for a := range everything {
137 if !kept[a] {
138 for _, f := range a.FactTypes {
139 gob.Register(f)
140 }
141 }
142 }
143
144 return analyzers
145 }
146
147 func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
148 seen := make(map[*analysis.Analyzer]bool)
149 var visitAll func([]*analysis.Analyzer)
150 visitAll = func(analyzers []*analysis.Analyzer) {
151 for _, a := range analyzers {
152 if !seen[a] {
153 seen[a] = true
154 visitAll(a.Requires)
155 }
156 }
157 }
158 visitAll(analyzers)
159 return seen
160 }
161
162 func printFlags() {
163 type jsonFlag struct {
164 Name string
165 Bool bool
166 Usage string
167 }
168 var flags []jsonFlag = nil
169 flag.VisitAll(func(f *flag.Flag) {
170
171
172
173 switch f.Name {
174 case "debug", "cpuprofile", "memprofile", "trace", "fix":
175 return
176 }
177
178 b, ok := f.Value.(interface{ IsBoolFlag() bool })
179 isBool := ok && b.IsBoolFlag()
180 flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
181 })
182 data, err := json.MarshalIndent(flags, "", "\t")
183 if err != nil {
184 log.Fatal(err)
185 }
186 os.Stdout.Write(data)
187 }
188
189
190
191
192
193
194
195 func addVersionFlag() {
196 if flag.Lookup("V") == nil {
197 flag.Var(versionFlag{}, "V", "print version and exit")
198 }
199 }
200
201
202 type versionFlag struct{}
203
204 func (versionFlag) IsBoolFlag() bool { return true }
205 func (versionFlag) Get() interface{} { return nil }
206 func (versionFlag) String() string { return "" }
207 func (versionFlag) Set(s string) error {
208 if s != "full" {
209 log.Fatalf("unsupported flag value: -V=%s", s)
210 }
211
212
213
214
215
216
217
218
219
220
221 progname := os.Args[0]
222 f, err := os.Open(progname)
223 if err != nil {
224 log.Fatal(err)
225 }
226 h := sha256.New()
227 if _, err := io.Copy(h, f); err != nil {
228 log.Fatal(err)
229 }
230 f.Close()
231 fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
232 progname, string(h.Sum(nil)))
233 os.Exit(0)
234 return nil
235 }
236
237
238
239
240
241
242
243 type triState int
244
245 const (
246 unset triState = iota
247 setTrue
248 setFalse
249 )
250
251 func triStateFlag(name string, value triState, usage string) *triState {
252 flag.Var(&value, name, usage)
253 return &value
254 }
255
256
257
258 func (ts *triState) Get() interface{} {
259 return *ts == setTrue
260 }
261
262 func (ts triState) isTrue() bool {
263 return ts == setTrue
264 }
265
266 func (ts *triState) Set(value string) error {
267 b, err := strconv.ParseBool(value)
268 if err != nil {
269
270
271 return fmt.Errorf("want true or false")
272 }
273 if b {
274 *ts = setTrue
275 } else {
276 *ts = setFalse
277 }
278 return nil
279 }
280
281 func (ts *triState) String() string {
282 switch *ts {
283 case unset:
284 return "true"
285 case setTrue:
286 return "true"
287 case setFalse:
288 return "false"
289 }
290 panic("not reached")
291 }
292
293 func (ts triState) IsBoolFlag() bool {
294 return true
295 }
296
297
298
299
300
301 var vetLegacyFlags = map[string]string{
302
303 "bool": "bools",
304 "buildtags": "buildtag",
305 "methods": "stdmethods",
306 "rangeloops": "loopclosure",
307
308
309 "compositewhitelist": "composites.whitelist",
310 "printfuncs": "printf.funcs",
311 "shadowstrict": "shadow.strict",
312 "unusedfuncs": "unusedresult.funcs",
313 "unusedstringmethods": "unusedresult.stringmethods",
314 }
315
316
317
318
319
320 func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
321 posn := fset.Position(diag.Pos)
322 fmt.Fprintf(os.Stderr, "%s: %s\n", posn, diag.Message)
323
324
325 if Context >= 0 {
326 posn := fset.Position(diag.Pos)
327 end := fset.Position(diag.End)
328 if !end.IsValid() {
329 end = posn
330 }
331 data, _ := ioutil.ReadFile(posn.Filename)
332 lines := strings.Split(string(data), "\n")
333 for i := posn.Line - Context; i <= end.Line+Context; i++ {
334 if 1 <= i && i <= len(lines) {
335 fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
336 }
337 }
338 }
339 }
340
341
342
343 type JSONTree map[string]map[string]interface{}
344
345
346
347 func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
348 var v interface{}
349 if err != nil {
350 type jsonError struct {
351 Err string `json:"error"`
352 }
353 v = jsonError{err.Error()}
354 } else if len(diags) > 0 {
355 type jsonDiagnostic struct {
356 Category string `json:"category,omitempty"`
357 Posn string `json:"posn"`
358 Message string `json:"message"`
359 }
360 var diagnostics []jsonDiagnostic
361
362
363 for _, f := range diags {
364 diagnostics = append(diagnostics, jsonDiagnostic{
365 Category: f.Category,
366 Posn: fset.Position(f.Pos).String(),
367 Message: f.Message,
368 })
369 }
370 v = diagnostics
371 }
372 if v != nil {
373 m, ok := tree[id]
374 if !ok {
375 m = make(map[string]interface{})
376 tree[id] = m
377 }
378 m[name] = v
379 }
380 }
381
382 func (tree JSONTree) Print() {
383 data, err := json.MarshalIndent(tree, "", "\t")
384 if err != nil {
385 log.Panicf("internal error: JSON marshaling failed: %v", err)
386 }
387 fmt.Printf("%s\n", data)
388 }
389
View as plain text