Source file src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go

     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.
     4  
     5  // Package analysisflags defines helpers for processing flags (-help,
     6  // -json, -fix, -diff, etc) common to unitchecker and
     7  // {single,multi}checker. It is not intended for broader use.
     8  package analysisflags
     9  
    10  import (
    11  	"crypto/sha256"
    12  	"encoding/gob"
    13  	"encoding/json"
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"log"
    18  	"os"
    19  	"strconv"
    20  
    21  	"golang.org/x/tools/go/analysis"
    22  )
    23  
    24  // flags common to all {single,multi,unit}checkers.
    25  var (
    26  	JSON    = false // -json
    27  	Context = -1    // -c=N: if N>0, display offending line plus N lines of context
    28  	Fix     bool    // -fix
    29  	Diff    bool    // -diff
    30  )
    31  
    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.
    36  //
    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 {
    48  		var prefix string
    49  
    50  		// Add -NAME flag to enable it.
    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  	// standard flags: -flags, -V.
    72  	printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
    73  	addVersionFlag()
    74  
    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`)
    78  	flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes")
    79  	flag.BoolVar(&Diff, "diff", false, "with -fix, don't update the files, but print a unified diff")
    80  
    81  	// Add shims for legacy vet flags to enable existing
    82  	// scripts that run vet to continue to work.
    83  	_ = flag.Bool("source", false, "no effect (deprecated)")
    84  	_ = flag.Bool("v", false, "no effect (deprecated)")
    85  	_ = flag.Bool("all", false, "no effect (deprecated)")
    86  	_ = flag.String("tags", "", "no effect (deprecated)")
    87  	for old, new := range vetLegacyFlags {
    88  		newFlag := flag.Lookup(new)
    89  		if newFlag != nil && flag.Lookup(old) == nil {
    90  			flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
    91  		}
    92  	}
    93  
    94  	flag.Parse() // (ExitOnError)
    95  
    96  	// -flags: print flags so that go vet knows which ones are legitimate.
    97  	if *printflags {
    98  		printFlags()
    99  		os.Exit(0)
   100  	}
   101  
   102  	everything := expand(analyzers)
   103  
   104  	// If any -NAME flag is true,  run only those analyzers. Otherwise,
   105  	// if any -NAME flag is false, run all but those analyzers.
   106  	if multi {
   107  		var hasTrue, hasFalse bool
   108  		for _, ts := range enabled {
   109  			switch *ts {
   110  			case setTrue:
   111  				hasTrue = true
   112  			case setFalse:
   113  				hasFalse = true
   114  			}
   115  		}
   116  
   117  		var keep []*analysis.Analyzer
   118  		if hasTrue {
   119  			for _, a := range analyzers {
   120  				if *enabled[a] == setTrue {
   121  					keep = append(keep, a)
   122  				}
   123  			}
   124  			analyzers = keep
   125  		} else if hasFalse {
   126  			for _, a := range analyzers {
   127  				if *enabled[a] != setFalse {
   128  					keep = append(keep, a)
   129  				}
   130  			}
   131  			analyzers = keep
   132  		}
   133  	}
   134  
   135  	// Register fact types of skipped analyzers
   136  	// in case we encounter them in imported files.
   137  	kept := expand(analyzers)
   138  	for a := range everything {
   139  		if !kept[a] {
   140  			for _, f := range a.FactTypes {
   141  				gob.Register(f)
   142  			}
   143  		}
   144  	}
   145  
   146  	return analyzers
   147  }
   148  
   149  func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
   150  	seen := make(map[*analysis.Analyzer]bool)
   151  	var visitAll func([]*analysis.Analyzer)
   152  	visitAll = func(analyzers []*analysis.Analyzer) {
   153  		for _, a := range analyzers {
   154  			if !seen[a] {
   155  				seen[a] = true
   156  				visitAll(a.Requires)
   157  			}
   158  		}
   159  	}
   160  	visitAll(analyzers)
   161  	return seen
   162  }
   163  
   164  func printFlags() {
   165  	type jsonFlag struct {
   166  		Name  string
   167  		Bool  bool
   168  		Usage string
   169  	}
   170  	var flags []jsonFlag = nil
   171  	flag.VisitAll(func(f *flag.Flag) {
   172  		// Don't report {single,multi}checker debugging
   173  		// flags or fix as these have no effect on unitchecker
   174  		// (as invoked by 'go vet').
   175  		switch f.Name {
   176  		case "debug", "cpuprofile", "memprofile", "trace", "fix":
   177  			return
   178  		}
   179  
   180  		b, ok := f.Value.(interface{ IsBoolFlag() bool })
   181  		isBool := ok && b.IsBoolFlag()
   182  		flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
   183  	})
   184  	data, err := json.MarshalIndent(flags, "", "\t")
   185  	if err != nil {
   186  		log.Fatal(err)
   187  	}
   188  	os.Stdout.Write(data)
   189  }
   190  
   191  // addVersionFlag registers a -V flag that, if set,
   192  // prints the executable version and exits 0.
   193  //
   194  // If the -V flag already exists — for example, because it was already
   195  // registered by a call to cmd/internal/objabi.AddVersionFlag — then
   196  // addVersionFlag does nothing.
   197  func addVersionFlag() {
   198  	if flag.Lookup("V") == nil {
   199  		flag.Var(versionFlag{}, "V", "print version and exit")
   200  	}
   201  }
   202  
   203  // versionFlag minimally complies with the -V protocol required by "go vet".
   204  type versionFlag struct{}
   205  
   206  func (versionFlag) IsBoolFlag() bool { return true }
   207  func (versionFlag) Get() any         { return nil }
   208  func (versionFlag) String() string   { return "" }
   209  func (versionFlag) Set(s string) error {
   210  	if s != "full" {
   211  		log.Fatalf("unsupported flag value: -V=%s (use -V=full)", s)
   212  	}
   213  
   214  	// This replicates the minimal subset of
   215  	// cmd/internal/objabi.AddVersionFlag, which is private to the
   216  	// go tool yet forms part of our command-line interface.
   217  	// TODO(adonovan): clarify the contract.
   218  
   219  	// Print the tool version so the build system can track changes.
   220  	// Formats:
   221  	//   $progname version devel ... buildID=...
   222  	//   $progname version go1.9.1
   223  	progname, err := os.Executable()
   224  	if err != nil {
   225  		return err
   226  	}
   227  	f, err := os.Open(progname)
   228  	if err != nil {
   229  		log.Fatal(err)
   230  	}
   231  	h := sha256.New()
   232  	if _, err := io.Copy(h, f); err != nil {
   233  		log.Fatal(err)
   234  	}
   235  	f.Close()
   236  	fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
   237  		progname, string(h.Sum(nil)))
   238  	os.Exit(0)
   239  	return nil
   240  }
   241  
   242  // A triState is a boolean that knows whether
   243  // it has been set to either true or false.
   244  // It is used to identify whether a flag appears;
   245  // the standard boolean flag cannot
   246  // distinguish missing from unset.
   247  // It also satisfies flag.Value.
   248  type triState int
   249  
   250  const (
   251  	unset triState = iota
   252  	setTrue
   253  	setFalse
   254  )
   255  
   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() any {
   259  	return *ts == setTrue
   260  }
   261  
   262  func (ts *triState) Set(value string) error {
   263  	b, err := strconv.ParseBool(value)
   264  	if err != nil {
   265  		// This error message looks poor but package "flag" adds
   266  		// "invalid boolean value %q for -NAME: %s"
   267  		return fmt.Errorf("want true or false")
   268  	}
   269  	if b {
   270  		*ts = setTrue
   271  	} else {
   272  		*ts = setFalse
   273  	}
   274  	return nil
   275  }
   276  
   277  func (ts *triState) String() string {
   278  	switch *ts {
   279  	case unset:
   280  		return "true"
   281  	case setTrue:
   282  		return "true"
   283  	case setFalse:
   284  		return "false"
   285  	}
   286  	panic("not reached")
   287  }
   288  
   289  func (ts triState) IsBoolFlag() bool {
   290  	return true
   291  }
   292  
   293  // Legacy flag support
   294  
   295  // vetLegacyFlags maps flags used by legacy vet to their corresponding
   296  // new names. The old names will continue to work.
   297  var vetLegacyFlags = map[string]string{
   298  	// Analyzer name changes
   299  	"bool":       "bools",
   300  	"buildtags":  "buildtag",
   301  	"methods":    "stdmethods",
   302  	"rangeloops": "loopclosure",
   303  
   304  	// Analyzer flags
   305  	"compositewhitelist":  "composites.whitelist",
   306  	"printfuncs":          "printf.funcs",
   307  	"shadowstrict":        "shadow.strict",
   308  	"unusedfuncs":         "unusedresult.funcs",
   309  	"unusedstringmethods": "unusedresult.stringmethods",
   310  }
   311  

View as plain text