1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package analysisflags defines helpers for processing flags of
6 // analysis driver tools.
23 "golang.org/x/tools/go/analysis"
26 // flags common to all {single,multi,unit}checkers.
29 Context = -1 // -c=N: if N>0, display offending line plus N lines of context
32 // Parse creates a flag for each of the analyzer's flags,
33 // including (in multi mode) a flag named after the analyzer,
34 // parses the flags, then filters and returns the list of
35 // analyzers enabled by flags.
37 // The result is intended to be passed to unitchecker.Run or checker.Run.
38 // Use in unitchecker.Run will gob.Register all fact types for the returned
39 // graph of analyzers but of course not the ones only reachable from
40 // dropped analyzers. To avoid inconsistency about which gob types are
41 // registered from run to run, Parse itself gob.Registers all the facts
42 // only reachable from dropped analyzers.
43 // This is not a particularly elegant API, but this is an internal package.
44 func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
45 // Connect each analysis flag to the command line as -analysis.flag.
46 enabled := make(map[*analysis.Analyzer]*triState)
47 for _, a := range analyzers {
50 // Add -NAME flag to enable it.
54 enable := new(triState)
55 enableUsage := "enable " + a.Name + " analysis"
56 flag.Var(enable, a.Name, enableUsage)
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)
66 name := prefix + f.Name
67 flag.Var(f.Value, name, f.Usage)
71 // standard flags: -flags, -V.
72 printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
75 // flags common to all checkers
76 flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
77 flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
79 // Add shims for legacy vet flags to enable existing
80 // scripts that run vet to continue to work.
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)
92 flag.Parse() // (ExitOnError)
94 // -flags: print flags so that go vet knows which ones are legitimate.
100 everything := expand(analyzers)
102 // If any -NAME flag is true, run only those analyzers. Otherwise,
103 // if any -NAME flag is false, run all but those analyzers.
105 var hasTrue, hasFalse bool
106 for _, ts := range enabled {
115 var keep []*analysis.Analyzer
117 for _, a := range analyzers {
118 if *enabled[a] == setTrue {
119 keep = append(keep, a)
124 for _, a := range analyzers {
125 if *enabled[a] != setFalse {
126 keep = append(keep, a)
133 // Register fact types of skipped analyzers
134 // in case we encounter them in imported files.
135 kept := expand(analyzers)
136 for a := range everything {
138 for _, f := range a.FactTypes {
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 {
163 type jsonFlag struct {
168 var flags []jsonFlag = nil
169 flag.VisitAll(func(f *flag.Flag) {
170 // Don't report {single,multi}checker debugging
171 // flags or fix as these have no effect on unitchecker
172 // (as invoked by 'go vet').
174 case "debug", "cpuprofile", "memprofile", "trace", "fix":
178 b, ok := f.Value.(interface{ IsBoolFlag() bool })
179 isBool := ok && b.IsBoolFlag()
180 flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
182 data, err := json.MarshalIndent(flags, "", "\t")
186 os.Stdout.Write(data)
189 // addVersionFlag registers a -V flag that, if set,
190 // prints the executable version and exits 0.
192 // If the -V flag already exists — for example, because it was already
193 // registered by a call to cmd/internal/objabi.AddVersionFlag — then
194 // addVersionFlag does nothing.
195 func addVersionFlag() {
196 if flag.Lookup("V") == nil {
197 flag.Var(versionFlag{}, "V", "print version and exit")
201 // versionFlag minimally complies with the -V protocol required by "go vet".
202 type versionFlag struct{}
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 {
209 log.Fatalf("unsupported flag value: -V=%s", s)
212 // This replicates the minimal subset of
213 // cmd/internal/objabi.AddVersionFlag, which is private to the
214 // go tool yet forms part of our command-line interface.
215 // TODO(adonovan): clarify the contract.
217 // Print the tool version so the build system can track changes.
219 // $progname version devel ... buildID=...
220 // $progname version go1.9.1
221 progname := os.Args[0]
222 f, err := os.Open(progname)
227 if _, err := io.Copy(h, f); err != nil {
231 fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
232 progname, string(h.Sum(nil)))
237 // A triState is a boolean that knows whether
238 // it has been set to either true or false.
239 // It is used to identify whether a flag appears;
240 // the standard boolean flag cannot
241 // distinguish missing from unset.
242 // It also satisfies flag.Value.
246 unset triState = iota
251 func triStateFlag(name string, value triState, usage string) *triState {
252 flag.Var(&value, name, usage)
256 // triState implements flag.Value, flag.Getter, and flag.boolFlag.
257 // They work like boolean flags: we can say vet -printf as well as vet -printf=true
258 func (ts *triState) Get() interface{} {
259 return *ts == setTrue
262 func (ts triState) isTrue() bool {
266 func (ts *triState) Set(value string) error {
267 b, err := strconv.ParseBool(value)
269 // This error message looks poor but package "flag" adds
270 // "invalid boolean value %q for -NAME: %s"
271 return fmt.Errorf("want true or false")
281 func (ts *triState) String() string {
293 func (ts triState) IsBoolFlag() bool {
297 // Legacy flag support
299 // vetLegacyFlags maps flags used by legacy vet to their corresponding
300 // new names. The old names will continue to work.
301 var vetLegacyFlags = map[string]string{
302 // Analyzer name changes
304 "buildtags": "buildtag",
305 "methods": "stdmethods",
306 "rangeloops": "loopclosure",
309 "compositewhitelist": "composites.whitelist",
310 "printfuncs": "printf.funcs",
311 "shadowstrict": "shadow.strict",
312 "unusedfuncs": "unusedresult.funcs",
313 "unusedstringmethods": "unusedresult.stringmethods",
316 // ---- output helpers common to all drivers ----
318 // PrintPlain prints a diagnostic in plain text form,
319 // with context specified by the -c flag.
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)
324 // -c=N: show offending line plus N lines of context.
326 posn := fset.Position(diag.Pos)
327 end := fset.Position(diag.End)
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])
341 // A JSONTree is a mapping from package ID to analysis name to result.
342 // Each result is either a jsonError or a list of jsonDiagnostic.
343 type JSONTree map[string]map[string]interface{}
345 // Add adds the result of analysis 'name' on package 'id'.
346 // The result is either a list of diagnostics or an error.
347 func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
350 type jsonError struct {
351 Err string `json:"error"`
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"`
360 var diagnostics []jsonDiagnostic
361 // TODO(matloob): Should the JSON diagnostics contain ranges?
362 // If so, how should they be formatted?
363 for _, f := range diags {
364 diagnostics = append(diagnostics, jsonDiagnostic{
365 Category: f.Category,
366 Posn: fset.Position(f.Pos).String(),
375 m = make(map[string]interface{})
382 func (tree JSONTree) Print() {
383 data, err := json.MarshalIndent(tree, "", "\t")
385 log.Panicf("internal error: JSON marshaling failed: %v", err)
387 fmt.Printf("%s\n", data)