Commit 398b54df authored by Alan Donovan's avatar Alan Donovan

cmd/go: make go vet query cmd/vet for its flags

Add -flags flag to cmd/vet that causes it to describe its flags as JSON.

go vet's "-vettool" flag has been replaced with an environment
variable, GOVETTOOL, for two reasons:

  1) we need its value before flag processing,
     because we must run vet to discover its flags.

  2) users may change the env var to opt in/out of the new vet tool
     during the upcoming transition to vet based on the analysis API.

Change-Id: I5d8f90817623022f4170b88fab3c92c9b2fbdc37
Reviewed-on: https://go-review.googlesource.com/c/142617
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBryan C. Mills <bcmills@google.com>
parent 4bea6c65
...@@ -5,9 +5,14 @@ ...@@ -5,9 +5,14 @@
package vet package vet
import ( import (
"bytes"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"log"
"os" "os"
"os/exec"
"path/filepath"
"strings" "strings"
"cmd/go/internal/base" "cmd/go/internal/base"
...@@ -16,72 +21,94 @@ import ( ...@@ -16,72 +21,94 @@ import (
"cmd/go/internal/work" "cmd/go/internal/work"
) )
const cmd = "vet" // go vet flag processing
//
// We query the flags of the tool specified by GOVETTOOL (default:
// cmd/vet) and accept any of those flags plus any flag valid for 'go
// build'. The tool must support -flags, which prints a description of
// its flags in JSON to stdout.
// vetFlagDefn is the set of flags we process. // GOVETTOOL specifies the vet command to run.
var vetFlagDefn = []*cmdflag.Defn{ // This must be an environment variable because
// Note: Some flags, in particular -tags and -v, are known to // we need it before flag processing, as we execute
// vet but also defined as build flags. This works fine, so we // $GOVETTOOL to discover the set of flags it supports.
// don't define them here but use AddBuildFlags to init them. //
// However some, like -x, are known to the build but not // Using an environment variable also makes it easy for users to opt in
// to vet. We handle them in vetFlags. // to (and later, opt out of) the new cmd/vet analysis driver during the
// transition. It is also used by tests.
var vetTool = os.Getenv("GOVETTOOL")
// local. // vetFlags processes the command line, splitting it at the first non-flag
{Name: "all", BoolVar: new(bool), PassToTest: true}, // into the list of flags and list of packages.
{Name: "asmdecl", BoolVar: new(bool), PassToTest: true}, func vetFlags(args []string) (passToVet, packageNames []string) {
{Name: "assign", BoolVar: new(bool), PassToTest: true}, // Query the vet command for its flags.
{Name: "atomic", BoolVar: new(bool), PassToTest: true}, tool := vetTool
{Name: "bool", BoolVar: new(bool), PassToTest: true}, if tool != "" {
{Name: "buildtags", BoolVar: new(bool), PassToTest: true}, var err error
{Name: "cgocall", BoolVar: new(bool), PassToTest: true}, tool, err = filepath.Abs(tool)
{Name: "composites", BoolVar: new(bool), PassToTest: true}, if err != nil {
{Name: "copylocks", BoolVar: new(bool), PassToTest: true}, log.Fatal(err)
{Name: "httpresponse", BoolVar: new(bool), PassToTest: true}, }
{Name: "lostcancel", BoolVar: new(bool), PassToTest: true}, } else {
{Name: "methods", BoolVar: new(bool), PassToTest: true}, tool = base.Tool("vet")
{Name: "nilfunc", BoolVar: new(bool), PassToTest: true}, }
{Name: "printf", BoolVar: new(bool), PassToTest: true}, out := new(bytes.Buffer)
{Name: "printfuncs", PassToTest: true}, vetcmd := exec.Command(tool, "-flags")
{Name: "rangeloops", BoolVar: new(bool), PassToTest: true}, vetcmd.Stdout = out
{Name: "shadow", BoolVar: new(bool), PassToTest: true}, if err := vetcmd.Run(); err != nil {
{Name: "shadowstrict", BoolVar: new(bool), PassToTest: true}, fmt.Fprintf(os.Stderr, "go vet: can't execute %s -flags: %v\n", tool, err)
{Name: "shift", BoolVar: new(bool), PassToTest: true}, os.Exit(2)
{Name: "source", BoolVar: new(bool), PassToTest: true}, }
{Name: "structtags", BoolVar: new(bool), PassToTest: true}, var analysisFlags []struct {
{Name: "tests", BoolVar: new(bool), PassToTest: true}, Name string
{Name: "unreachable", BoolVar: new(bool), PassToTest: true}, Bool bool
{Name: "unsafeptr", BoolVar: new(bool), PassToTest: true}, Usage string
{Name: "unusedfuncs", PassToTest: true}, }
{Name: "unusedresult", BoolVar: new(bool), PassToTest: true}, if err := json.Unmarshal(out.Bytes(), &analysisFlags); err != nil {
{Name: "unusedstringmethods", PassToTest: true}, fmt.Fprintf(os.Stderr, "go vet: can't unmarshal JSON from %s -flags: %v", tool, err)
} os.Exit(2)
}
var vetTool string // Add vet's flags to vetflagDefn.
//
// Some flags, in particular -tags and -v, are known to vet but
// also defined as build flags. This works fine, so we don't
// define them here but use AddBuildFlags to init them.
// However some, like -x, are known to the build but not to vet.
var vetFlagDefn []*cmdflag.Defn
for _, f := range analysisFlags {
switch f.Name {
case "tags", "v":
continue
}
defn := &cmdflag.Defn{
Name: f.Name,
PassToTest: true,
}
if f.Bool {
defn.BoolVar = new(bool)
}
vetFlagDefn = append(vetFlagDefn, defn)
}
// add build flags to vetFlagDefn. // Add build flags to vetFlagDefn.
func init() {
cmdflag.AddKnownFlags("vet", vetFlagDefn)
var cmd base.Command var cmd base.Command
work.AddBuildFlags(&cmd) work.AddBuildFlags(&cmd)
cmd.Flag.StringVar(&vetTool, "vettool", "", "path to vet tool binary") // for cmd/vet tests; undocumented for now
cmd.Flag.VisitAll(func(f *flag.Flag) { cmd.Flag.VisitAll(func(f *flag.Flag) {
vetFlagDefn = append(vetFlagDefn, &cmdflag.Defn{ vetFlagDefn = append(vetFlagDefn, &cmdflag.Defn{
Name: f.Name, Name: f.Name,
Value: f.Value, Value: f.Value,
}) })
}) })
}
// vetFlags processes the command line, splitting it at the first non-flag // Process args.
// into the list of flags and list of packages.
func vetFlags(args []string) (passToVet, packageNames []string) {
args = str.StringList(cmdflag.FindGOFLAGS(vetFlagDefn), args) args = str.StringList(cmdflag.FindGOFLAGS(vetFlagDefn), args)
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
if !strings.HasPrefix(args[i], "-") { if !strings.HasPrefix(args[i], "-") {
return args[:i], args[i:] return args[:i], args[i:]
} }
f, value, extraWord := cmdflag.Parse(cmd, vetFlagDefn, args, i) f, value, extraWord := cmdflag.Parse("vet", vetFlagDefn, args, i)
if f == nil { if f == nil {
fmt.Fprintf(os.Stderr, "vet: flag %q not defined\n", args[i]) fmt.Fprintf(os.Stderr, "vet: flag %q not defined\n", args[i])
fmt.Fprintf(os.Stderr, "Run \"go help vet\" for more information\n") fmt.Fprintf(os.Stderr, "Run \"go help vet\" for more information\n")
......
...@@ -178,7 +178,7 @@ func (b *Builder) toolID(name string) string { ...@@ -178,7 +178,7 @@ func (b *Builder) toolID(name string) string {
path := base.Tool(name) path := base.Tool(name)
desc := "go tool " + name desc := "go tool " + name
// Special case: undocumented -vettool overrides usual vet, for testing vet. // Special case: undocumented $GOVETTOOL overrides usual vet, for testing vet.
if name == "vet" && VetTool != "" { if name == "vet" && VetTool != "" {
path = VetTool path = VetTool
desc = VetTool desc = VetTool
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"go/types" "go/types"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
...@@ -31,10 +32,9 @@ import ( ...@@ -31,10 +32,9 @@ import (
"cmd/internal/objabi" "cmd/internal/objabi"
) )
// Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go.
var ( var (
verbose = flag.Bool("v", false, "verbose") verbose = flag.Bool("v", false, "verbose")
flags = flag.Bool("flags", false, "print flags in JSON")
source = flag.Bool("source", false, "import from source instead of compiled object files") source = flag.Bool("source", false, "import from source instead of compiled object files")
tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing") tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing")
tagList = []string{} // exploded version of tags flag; set in main tagList = []string{} // exploded version of tags flag; set in main
...@@ -259,6 +259,32 @@ func main() { ...@@ -259,6 +259,32 @@ func main() {
flag.Usage = Usage flag.Usage = Usage
flag.Parse() flag.Parse()
// -flags: print flags as JSON. Used by go vet.
if *flags {
type jsonFlag struct {
Name string
Bool bool
Usage string
}
var jsonFlags []jsonFlag
flag.VisitAll(func(f *flag.Flag) {
isBool := false
switch v := f.Value.(type) {
case interface{ BoolFlag() bool }:
isBool = v.BoolFlag()
case *triState:
isBool = true // go vet should treat it as boolean
}
jsonFlags = append(jsonFlags, jsonFlag{f.Name, isBool, f.Usage})
})
data, err := json.MarshalIndent(jsonFlags, "", "\t")
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(data)
os.Exit(0)
}
// If any flag is set, we run only those checks requested. // If any flag is set, we run only those checks requested.
// If all flag is set true or if no flags are set true, set all the non-experimental ones // If all flag is set true or if no flags are set true, set all the non-experimental ones
// not explicitly set (in effect, set the "-all" flag). // not explicitly set (in effect, set the "-all" flag).
......
...@@ -118,11 +118,12 @@ func TestVetPrint(t *testing.T) { ...@@ -118,11 +118,12 @@ func TestVetPrint(t *testing.T) {
Build(t) Build(t)
file := filepath.Join("testdata", "print.go") file := filepath.Join("testdata", "print.go")
cmd := exec.Command( cmd := exec.Command(
"go", "vet", "-vettool="+binary, "go", "vet",
"-printf", "-printf",
"-printfuncs=Warn:1,Warnf:1", "-printfuncs=Warn:1,Warnf:1",
file, file,
) )
cmd.Env = append(os.Environ(), "GOVETTOOL="+binary)
errchk(cmd, []string{file}, t) errchk(cmd, []string{file}, t)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment