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 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  	"log"
    18  	"os"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"golang.org/x/tools/go/analysis"
    23  )
    24  
    25  // flags common to all {single,multi,unit}checkers.
    26  var (
    27  	JSON    = false // -json
    28  	Context = -1    // -c=N: if N>0, display offending line plus N lines of context
    29  )
    30  
    31  // Parse creates a flag for each of the analyzer's flags,
    32  // including (in multi mode) a flag named after the analyzer,
    33  // parses the flags, then filters and returns the list of
    34  // analyzers enabled by flags.
    35  //
    36  // The result is intended to be passed to unitchecker.Run or checker.Run.
    37  // Use in unitchecker.Run will gob.Register all fact types for the returned
    38  // graph of analyzers but of course not the ones only reachable from
    39  // dropped analyzers. To avoid inconsistency about which gob types are
    40  // registered from run to run, Parse itself gob.Registers all the facts
    41  // only reachable from dropped analyzers.
    42  // This is not a particularly elegant API, but this is an internal package.
    43  func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
    44  	// Connect each analysis flag to the command line as -analysis.flag.
    45  	enabled := make(map[*analysis.Analyzer]*triState)
    46  	for _, a := range analyzers {
    47  		var prefix string
    48  
    49  		// Add -NAME flag to enable it.
    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  	// standard flags: -flags, -V.
    71  	printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
    72  	addVersionFlag()
    73  
    74  	// flags common to all checkers
    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  	// Add shims for legacy vet flags to enable existing
    79  	// scripts that run vet to continue to work.
    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() // (ExitOnError)
    92  
    93  	// -flags: print flags so that go vet knows which ones are legitimate.
    94  	if *printflags {
    95  		printFlags()
    96  		os.Exit(0)
    97  	}
    98  
    99  	everything := expand(analyzers)
   100  
   101  	// If any -NAME flag is true,  run only those analyzers. Otherwise,
   102  	// if any -NAME flag is false, run all but those analyzers.
   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  	// Register fact types of skipped analyzers
   133  	// in case we encounter them in imported files.
   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  		// Don't report {single,multi}checker debugging
   170  		// flags or fix as these have no effect on unitchecker
   171  		// (as invoked by 'go vet').
   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  // addVersionFlag registers a -V flag that, if set,
   189  // prints the executable version and exits 0.
   190  //
   191  // If the -V flag already exists — for example, because it was already
   192  // registered by a call to cmd/internal/objabi.AddVersionFlag — then
   193  // addVersionFlag does nothing.
   194  func addVersionFlag() {
   195  	if flag.Lookup("V") == nil {
   196  		flag.Var(versionFlag{}, "V", "print version and exit")
   197  	}
   198  }
   199  
   200  // versionFlag minimally complies with the -V protocol required by "go vet".
   201  type versionFlag struct{}
   202  
   203  func (versionFlag) IsBoolFlag() bool { return true }
   204  func (versionFlag) Get() any         { 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  	// This replicates the minimal subset of
   212  	// cmd/internal/objabi.AddVersionFlag, which is private to the
   213  	// go tool yet forms part of our command-line interface.
   214  	// TODO(adonovan): clarify the contract.
   215  
   216  	// Print the tool version so the build system can track changes.
   217  	// Formats:
   218  	//   $progname version devel ... buildID=...
   219  	//   $progname version go1.9.1
   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  // A triState is a boolean that knows whether
   240  // it has been set to either true or false.
   241  // It is used to identify whether a flag appears;
   242  // the standard boolean flag cannot
   243  // distinguish missing from unset.
   244  // It also satisfies flag.Value.
   245  type triState int
   246  
   247  const (
   248  	unset triState = iota
   249  	setTrue
   250  	setFalse
   251  )
   252  
   253  // triState implements flag.Value, flag.Getter, and flag.boolFlag.
   254  // They work like boolean flags: we can say vet -printf as well as vet -printf=true
   255  func (ts *triState) Get() any {
   256  	return *ts == setTrue
   257  }
   258  
   259  func (ts *triState) Set(value string) error {
   260  	b, err := strconv.ParseBool(value)
   261  	if err != nil {
   262  		// This error message looks poor but package "flag" adds
   263  		// "invalid boolean value %q for -NAME: %s"
   264  		return fmt.Errorf("want true or false")
   265  	}
   266  	if b {
   267  		*ts = setTrue
   268  	} else {
   269  		*ts = setFalse
   270  	}
   271  	return nil
   272  }
   273  
   274  func (ts *triState) String() string {
   275  	switch *ts {
   276  	case unset:
   277  		return "true"
   278  	case setTrue:
   279  		return "true"
   280  	case setFalse:
   281  		return "false"
   282  	}
   283  	panic("not reached")
   284  }
   285  
   286  func (ts triState) IsBoolFlag() bool {
   287  	return true
   288  }
   289  
   290  // Legacy flag support
   291  
   292  // vetLegacyFlags maps flags used by legacy vet to their corresponding
   293  // new names. The old names will continue to work.
   294  var vetLegacyFlags = map[string]string{
   295  	// Analyzer name changes
   296  	"bool":       "bools",
   297  	"buildtags":  "buildtag",
   298  	"methods":    "stdmethods",
   299  	"rangeloops": "loopclosure",
   300  
   301  	// Analyzer flags
   302  	"compositewhitelist":  "composites.whitelist",
   303  	"printfuncs":          "printf.funcs",
   304  	"shadowstrict":        "shadow.strict",
   305  	"unusedfuncs":         "unusedresult.funcs",
   306  	"unusedstringmethods": "unusedresult.stringmethods",
   307  }
   308  
   309  // ---- output helpers common to all drivers ----
   310  //
   311  // These functions should not depend on global state (flags)!
   312  // Really they belong in a different package.
   313  
   314  // TODO(adonovan): don't accept an io.Writer if we don't report errors.
   315  // Either accept a bytes.Buffer (infallible), or return a []byte.
   316  
   317  // PrintPlain prints a diagnostic in plain text form.
   318  // If contextLines is nonnegative, it also prints the
   319  // offending line plus this many lines of context.
   320  func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
   321  	print := func(pos, end token.Pos, message string) {
   322  		posn := fset.Position(pos)
   323  		fmt.Fprintf(out, "%s: %s\n", posn, message)
   324  
   325  		// show offending line plus N lines of context.
   326  		if contextLines >= 0 {
   327  			end := fset.Position(end)
   328  			if !end.IsValid() {
   329  				end = posn
   330  			}
   331  			// TODO(adonovan): highlight the portion of the line indicated
   332  			// by pos...end using ASCII art, terminal colors, etc?
   333  			data, _ := os.ReadFile(posn.Filename)
   334  			lines := strings.Split(string(data), "\n")
   335  			for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
   336  				if 1 <= i && i <= len(lines) {
   337  					fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
   338  				}
   339  			}
   340  		}
   341  	}
   342  
   343  	print(diag.Pos, diag.End, diag.Message)
   344  	for _, rel := range diag.Related {
   345  		print(rel.Pos, rel.End, "\t"+rel.Message)
   346  	}
   347  }
   348  
   349  // A JSONTree is a mapping from package ID to analysis name to result.
   350  // Each result is either a jsonError or a list of JSONDiagnostic.
   351  type JSONTree map[string]map[string]any
   352  
   353  // A TextEdit describes the replacement of a portion of a file.
   354  // Start and End are zero-based half-open indices into the original byte
   355  // sequence of the file, and New is the new text.
   356  type JSONTextEdit struct {
   357  	Filename string `json:"filename"`
   358  	Start    int    `json:"start"`
   359  	End      int    `json:"end"`
   360  	New      string `json:"new"`
   361  }
   362  
   363  // A JSONSuggestedFix describes an edit that should be applied as a whole or not
   364  // at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
   365  // consists of multiple non-contiguous edits.
   366  type JSONSuggestedFix struct {
   367  	Message string         `json:"message"`
   368  	Edits   []JSONTextEdit `json:"edits"`
   369  }
   370  
   371  // A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic.
   372  //
   373  // TODO(matloob): include End position if present.
   374  type JSONDiagnostic struct {
   375  	Category       string                   `json:"category,omitempty"`
   376  	Posn           string                   `json:"posn"` // e.g. "file.go:line:column"
   377  	Message        string                   `json:"message"`
   378  	SuggestedFixes []JSONSuggestedFix       `json:"suggested_fixes,omitempty"`
   379  	Related        []JSONRelatedInformation `json:"related,omitempty"`
   380  }
   381  
   382  // A JSONRelated describes a secondary position and message related to
   383  // a primary diagnostic.
   384  //
   385  // TODO(adonovan): include End position if present.
   386  type JSONRelatedInformation struct {
   387  	Posn    string `json:"posn"` // e.g. "file.go:line:column"
   388  	Message string `json:"message"`
   389  }
   390  
   391  // Add adds the result of analysis 'name' on package 'id'.
   392  // The result is either a list of diagnostics or an error.
   393  func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
   394  	var v any
   395  	if err != nil {
   396  		type jsonError struct {
   397  			Err string `json:"error"`
   398  		}
   399  		v = jsonError{err.Error()}
   400  	} else if len(diags) > 0 {
   401  		diagnostics := make([]JSONDiagnostic, 0, len(diags))
   402  		for _, f := range diags {
   403  			var fixes []JSONSuggestedFix
   404  			for _, fix := range f.SuggestedFixes {
   405  				var edits []JSONTextEdit
   406  				for _, edit := range fix.TextEdits {
   407  					edits = append(edits, JSONTextEdit{
   408  						Filename: fset.Position(edit.Pos).Filename,
   409  						Start:    fset.Position(edit.Pos).Offset,
   410  						End:      fset.Position(edit.End).Offset,
   411  						New:      string(edit.NewText),
   412  					})
   413  				}
   414  				fixes = append(fixes, JSONSuggestedFix{
   415  					Message: fix.Message,
   416  					Edits:   edits,
   417  				})
   418  			}
   419  			var related []JSONRelatedInformation
   420  			for _, r := range f.Related {
   421  				related = append(related, JSONRelatedInformation{
   422  					Posn:    fset.Position(r.Pos).String(),
   423  					Message: r.Message,
   424  				})
   425  			}
   426  			jdiag := JSONDiagnostic{
   427  				Category:       f.Category,
   428  				Posn:           fset.Position(f.Pos).String(),
   429  				Message:        f.Message,
   430  				SuggestedFixes: fixes,
   431  				Related:        related,
   432  			}
   433  			diagnostics = append(diagnostics, jdiag)
   434  		}
   435  		v = diagnostics
   436  	}
   437  	if v != nil {
   438  		m, ok := tree[id]
   439  		if !ok {
   440  			m = make(map[string]any)
   441  			tree[id] = m
   442  		}
   443  		m[name] = v
   444  	}
   445  }
   446  
   447  func (tree JSONTree) Print(out io.Writer) error {
   448  	data, err := json.MarshalIndent(tree, "", "\t")
   449  	if err != nil {
   450  		log.Panicf("internal error: JSON marshaling failed: %v", err)
   451  	}
   452  	_, err = fmt.Fprintf(out, "%s\n", data)
   453  	return err
   454  }
   455  

View as plain text