...

Source file src/pkg/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 of
     6	// analysis driver tools.
     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	// flags common to all {single,multi,unit}checkers.
    27	var (
    28		JSON    = false // -json
    29		Context = -1    // -c=N: if N>0, display offending line plus N lines of context
    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	
    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)
    89			}
    90		}
    91	
    92		flag.Parse() // (ExitOnError)
    93	
    94		// -flags: print flags so that go vet knows which ones are legitimate.
    95		if *printflags {
    96			printFlags()
    97			os.Exit(0)
    98		}
    99	
   100		everything := expand(analyzers)
   101	
   102		// If any -NAME flag is true,  run only those analyzers. Otherwise,
   103		// if any -NAME flag is false, run all but those analyzers.
   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		// Register fact types of skipped analyzers
   134		// in case we encounter them in imported files.
   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			// Don't report {single,multi}checker debugging
   171			// flags as these have no effect on unitchecker
   172			// (as invoked by 'go vet').
   173			switch f.Name {
   174			case "debug", "cpuprofile", "memprofile", "trace":
   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	// addVersionFlag registers a -V flag that, if set,
   190	// prints the executable version and exits 0.
   191	//
   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")
   198		}
   199	}
   200	
   201	// versionFlag minimally complies with the -V protocol required by "go vet".
   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		// This replicates the miminal 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.
   216	
   217		// Print the tool version so the build system can track changes.
   218		// Formats:
   219		//   $progname version devel ... buildID=...
   220		//   $progname version go1.9.1
   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	// 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.
   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	// 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
   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			// 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")
   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	// Legacy flag support
   298	
   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
   303		"bool":       "bools",
   304		"buildtags":  "buildtag",
   305		"methods":    "stdmethods",
   306		"rangeloops": "loopclosure",
   307	
   308		// Analyzer flags
   309		"compositewhitelist":  "composites.whitelist",
   310		"printfuncs":          "printf.funcs",
   311		"shadowstrict":        "shadow.strict",
   312		"unusedfuncs":         "unusedresult.funcs",
   313		"unusedstringmethods": "unusedresult.stringmethods",
   314	}
   315	
   316	// ---- output helpers common to all drivers ----
   317	
   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)
   323	
   324		// -c=N: show offending line plus N lines of context.
   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	// 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{}
   344	
   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) {
   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			// 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(),
   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 marshalling failed: %v", err)
   386		}
   387		fmt.Printf("%s\n", data)
   388	}
   389	

View as plain text