// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package vet import ( "bytes" "encoding/json" "errors" "flag" "fmt" "log" "os" "os/exec" "path/filepath" "strings" "cmd/go/internal/base" "cmd/go/internal/cmdflag" "cmd/go/internal/work" ) // go vet flag processing // // We query the flags of the tool specified by -vettool 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. // vetTool specifies the vet command to run. // Any tool that supports the (still unpublished) vet // command-line protocol may be supplied; see // golang.org/x/tools/go/analysis/unitchecker for one // implementation. It is also used by tests. // // The default behavior (vetTool=="") runs 'go tool vet'. var vetTool string // -vettool func init() { work.AddBuildFlags(CmdVet, work.DefaultBuildFlags) CmdVet.Flag.StringVar(&vetTool, "vettool", "", "") } func parseVettoolFlag(args []string) { // Extract -vettool by ad hoc flag processing: // its value is needed even before we can declare // the flags available during main flag processing. for i, arg := range args { if arg == "-vettool" || arg == "--vettool" { if i+1 >= len(args) { log.Fatalf("%s requires a filename", arg) } vetTool = args[i+1] return } else if strings.HasPrefix(arg, "-vettool=") || strings.HasPrefix(arg, "--vettool=") { vetTool = arg[strings.IndexByte(arg, '=')+1:] return } } } // vetFlags processes the command line, splitting it at the first non-flag // into the list of flags and list of packages. func vetFlags(args []string) (passToVet, packageNames []string) { parseVettoolFlag(args) // Query the vet command for its flags. var tool string if vetTool == "" { tool = base.Tool("vet") } else { var err error tool, err = filepath.Abs(vetTool) if err != nil { log.Fatal(err) } } out := new(bytes.Buffer) vetcmd := exec.Command(tool, "-flags") vetcmd.Stdout = out if err := vetcmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "go: can't execute %s -flags: %v\n", tool, err) base.SetExitStatus(2) base.Exit() } var analysisFlags []struct { Name string Bool bool Usage string } if err := json.Unmarshal(out.Bytes(), &analysisFlags); err != nil { fmt.Fprintf(os.Stderr, "go: can't unmarshal JSON from %s -flags: %v", tool, err) base.SetExitStatus(2) base.Exit() } // Add vet's flags to CmdVet.Flag. // // Some flags, in particular -tags and -v, are known to vet but // also defined as build flags. This works fine, so we omit duplicates here. // However some, like -x, are known to the build but not to vet. isVetFlag := make(map[string]bool, len(analysisFlags)) cf := CmdVet.Flag for _, f := range analysisFlags { isVetFlag[f.Name] = true if cf.Lookup(f.Name) == nil { if f.Bool { cf.Bool(f.Name, false, "") } else { cf.String(f.Name, "", "") } } } // Record the set of vet tool flags set by GOFLAGS. We want to pass them to // the vet tool, but only if they aren't overridden by an explicit argument. base.SetFromGOFLAGS(&CmdVet.Flag) addFromGOFLAGS := map[string]bool{} CmdVet.Flag.Visit(func(f *flag.Flag) { if isVetFlag[f.Name] { addFromGOFLAGS[f.Name] = true } }) explicitFlags := make([]string, 0, len(args)) for len(args) > 0 { f, remainingArgs, err := cmdflag.ParseOne(&CmdVet.Flag, args) if errors.Is(err, flag.ErrHelp) { exitWithUsage() } if errors.Is(err, cmdflag.ErrFlagTerminator) { // All remaining args must be package names, but the flag terminator is // not included. packageNames = remainingArgs break } if nf := (cmdflag.NonFlagError{}); errors.As(err, &nf) { // Everything from here on out — including the argument we just consumed — // must be a package name. packageNames = args break } if err != nil { fmt.Fprintln(os.Stderr, err) exitWithUsage() } if isVetFlag[f.Name] { // Forward the raw arguments rather than cleaned equivalents, just in // case the vet tool parses them idiosyncratically. explicitFlags = append(explicitFlags, args[:len(args)-len(remainingArgs)]...) // This flag has been overridden explicitly, so don't forward its implicit // value from GOFLAGS. delete(addFromGOFLAGS, f.Name) } args = remainingArgs } // Prepend arguments from GOFLAGS before other arguments. CmdVet.Flag.Visit(func(f *flag.Flag) { if addFromGOFLAGS[f.Name] { passToVet = append(passToVet, fmt.Sprintf("-%s=%s", f.Name, f.Value)) } }) passToVet = append(passToVet, explicitFlags...) return passToVet, packageNames } func exitWithUsage() { fmt.Fprintf(os.Stderr, "usage: %s\n", CmdVet.UsageLine) fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", CmdVet.LongName()) // This part is additional to what (*Command).Usage does: cmd := "go tool vet" if vetTool != "" { cmd = vetTool } fmt.Fprintf(os.Stderr, "Run '%s help' for a full list of flags and analyzers.\n", cmd) fmt.Fprintf(os.Stderr, "Run '%s -help' for an overview.\n", cmd) base.SetExitStatus(2) base.Exit() }