diff options
Diffstat (limited to 'src/cmd/go/internal/vet')
-rw-r--r-- | src/cmd/go/internal/vet/vet.go | 124 | ||||
-rw-r--r-- | src/cmd/go/internal/vet/vetflag.go | 191 |
2 files changed, 315 insertions, 0 deletions
diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go new file mode 100644 index 0000000..c73fa5b --- /dev/null +++ b/src/cmd/go/internal/vet/vet.go @@ -0,0 +1,124 @@ +// Copyright 2011 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 implements the “go vet” command. +package vet + +import ( + "context" + "fmt" + "path/filepath" + + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/load" + "cmd/go/internal/modload" + "cmd/go/internal/trace" + "cmd/go/internal/work" +) + +// Break init loop. +func init() { + CmdVet.Run = runVet +} + +var CmdVet = &base.Command{ + CustomFlags: true, + UsageLine: "go vet [-C dir] [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages]", + Short: "report likely mistakes in packages", + Long: ` +Vet runs the Go vet command on the packages named by the import paths. + +For more about vet and its flags, see 'go doc cmd/vet'. +For more about specifying packages, see 'go help packages'. +For a list of checkers and their flags, see 'go tool vet help'. +For details of a specific checker such as 'printf', see 'go tool vet help printf'. + +The -C flag changes to dir before running the 'go vet' command. +The -n flag prints commands that would be executed. +The -x flag prints commands as they are executed. + +The -vettool=prog flag selects a different analysis tool with alternative +or additional checks. +For example, the 'shadow' analyzer can be built and run using these commands: + + go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest + go vet -vettool=$(which shadow) + +The build flags supported by go vet are those that control package resolution +and execution, such as -n, -x, -v, -tags, and -toolexec. +For more about these flags, see 'go help build'. + +See also: go fmt, go fix. + `, +} + +func runVet(ctx context.Context, cmd *base.Command, args []string) { + vetFlags, pkgArgs := vetFlags(args) + modload.InitWorkfile() // The vet command does custom flag processing; initialize workspaces after that. + + if cfg.DebugTrace != "" { + var close func() error + var err error + ctx, close, err = trace.Start(ctx, cfg.DebugTrace) + if err != nil { + base.Fatalf("failed to start trace: %v", err) + } + defer func() { + if err := close(); err != nil { + base.Fatalf("failed to stop trace: %v", err) + } + }() + } + + ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command")) + defer span.Done() + + work.BuildInit() + work.VetFlags = vetFlags + if len(vetFlags) > 0 { + work.VetExplicit = true + } + if vetTool != "" { + var err error + work.VetTool, err = filepath.Abs(vetTool) + if err != nil { + base.Fatalf("%v", err) + } + } + + pkgOpts := load.PackageOpts{ModResolveTests: true} + pkgs := load.PackagesAndErrors(ctx, pkgOpts, pkgArgs) + load.CheckPackageErrors(pkgs) + if len(pkgs) == 0 { + base.Fatalf("no packages to vet") + } + + b := work.NewBuilder("") + defer func() { + if err := b.Close(); err != nil { + base.Fatalf("go: %v", err) + } + }() + + root := &work.Action{Mode: "go vet"} + for _, p := range pkgs { + _, ptest, pxtest, err := load.TestPackagesFor(ctx, pkgOpts, p, nil) + if err != nil { + base.Errorf("%v", err) + continue + } + if len(ptest.GoFiles) == 0 && len(ptest.CgoFiles) == 0 && pxtest == nil { + base.Errorf("go: can't vet %s: no Go files in %s", p.ImportPath, p.Dir) + continue + } + if len(ptest.GoFiles) > 0 || len(ptest.CgoFiles) > 0 { + root.Deps = append(root.Deps, b.VetAction(work.ModeBuild, work.ModeBuild, ptest)) + } + if pxtest != nil { + root.Deps = append(root.Deps, b.VetAction(work.ModeBuild, work.ModeBuild, pxtest)) + } + } + b.Do(ctx, root) +} diff --git a/src/cmd/go/internal/vet/vetflag.go b/src/cmd/go/internal/vet/vetflag.go new file mode 100644 index 0000000..eb7af65 --- /dev/null +++ b/src/cmd/go/internal/vet/vetflag.go @@ -0,0 +1,191 @@ +// 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() +} |