a replacement for `go tool doc`: it takes the same flags and arguments and
has the same behavior.
+<!-- go.dev/issue/75432 -->
+The `go fix` command, following the pattern of `go vet` in Go 1.10,
+now uses the Go analysis framework (`golang.org/x/tools/go/analysis`).
+This means the same analyzers that provide diagnostics in `go vet`
+can be used to suggest and apply fixes in `go fix`.
+The `go fix` command's historical fixers, all of which were obsolete,
+have been removed and replaced by a suite of new analyzers that
+offer fixes to use newer features of the language and library.
+<!-- I'll write a blog post that discusses this at length. --adonovan -->
+
### Cgo {#cgo}
binExesIncludedInDistpack = []string{"cmd/go", "cmd/gofmt"}
// Keep in sync with the filter in cmd/distpack/pack.go.
- toolsIncludedInDistpack = []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/cover", "cmd/link", "cmd/preprofile", "cmd/vet"}
+ toolsIncludedInDistpack = []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/cover", "cmd/fix", "cmd/link", "cmd/preprofile", "cmd/vet"}
// We could install all tools in "cmd", but is unnecessary because we will
// remove them in distpack, so instead install the tools that will actually
+++ /dev/null
-// 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.
-
-/*
-Fix finds Go programs that use old APIs and rewrites them to use
-newer ones. After you update to a new Go release, fix helps make
-the necessary changes to your programs.
-
-Usage:
-
- go tool fix [ignored...]
-
-This tool is currently in transition. All its historical fixers were
-long obsolete and have been removed, so it is currently a no-op. In
-due course the tool will integrate with the Go analysis framework
-(golang.org/x/tools/go/analysis) and run a modern suite of fix
-algorithms; see https://go.dev/issue/71859.
-*/
-package main
-// Copyright 2011 The Go Authors. All rights reserved.
+// Copyright 2025 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.
+/*
+Fix is a tool executed by "go fix" to update Go programs that use old
+features of the language and library and rewrite them to use newer
+ones. After you update to a new Go release, fix helps make the
+necessary changes to your programs.
+
+See the documentation for "go fix" for how to run this command.
+You can provide an alternative tool using "go fix -fixtool=..."
+
+Run "go tool fix help" to see the list of analyzers supported by this
+program.
+
+See [golang.org/x/tools/go/analysis] for information on how to write
+an analyzer that can suggest fixes.
+*/
package main
import (
- "flag"
- "fmt"
- "os"
-)
+ "cmd/internal/objabi"
+ "cmd/internal/telemetry/counter"
-var (
- _ = flag.Bool("diff", false, "obsolete, no effect")
- _ = flag.String("go", "", "obsolete, no effect")
- _ = flag.String("r", "", "obsolete, no effect")
- _ = flag.String("force", "", "obsolete, no effect")
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/buildtag"
+ "golang.org/x/tools/go/analysis/passes/hostport"
+ "golang.org/x/tools/go/analysis/unitchecker"
)
-func usage() {
- fmt.Fprintf(os.Stderr, "usage: go tool fix [-diff] [-r ignored] [-force ignored] ...\n")
- flag.PrintDefaults()
- os.Exit(2)
-}
-
func main() {
- flag.Usage = usage
- flag.Parse()
+ // Keep consistent with cmd/vet/main.go!
+ counter.Open()
+ objabi.AddVersionFlag()
+ counter.Inc("fix/invocations")
+
+ unitchecker.Main(suite...) // (never returns)
+}
- os.Exit(0)
+// The fix suite analyzers produce fixes that are safe to apply.
+// (Diagnostics may not describe actual problems,
+// but their fixes must be unambiguously safe to apply.)
+var suite = []*analysis.Analyzer{
+ buildtag.Analyzer,
+ hostport.Analyzer,
+ // TODO(adonovan): now the modernize (proposal #75266) and
+ // inline (proposal #75267) analyzers are published, revendor
+ // x/tools and add them here.
+ //
+ // TODO(adonovan):add any other vet analyzers whose fixes are always safe.
+ // Candidates to audit: sigchanyzer, printf, assign, unreachable.
+ // Rejected:
+ // - composites: some types (e.g. PointXY{1,2}) don't want field names.
+ // - timeformat: flipping MM/DD is a behavior change, but the code
+ // could potentially be a workaround for another bug.
+ // - stringintconv: offers two fixes, user input required to choose.
+ // - fieldalignment: poor signal/noise; fix could be a regression.
}
// clean remove object files and cached files
// doc show documentation for package or symbol
// env print Go environment information
-// fix update packages to use new APIs
+// fix apply fixes suggested by static checkers
// fmt gofmt (reformat) package sources
// generate generate Go files by processing source
// get add dependencies to current module and install them
//
// For more about environment variables, see 'go help environment'.
//
-// # Update packages to use new APIs
+// # Apply fixes suggested by static checkers
//
// Usage:
//
-// go fix [-fix list] [packages]
+// go fix [build flags] [-fixtool prog] [fix flags] [packages]
//
-// Fix runs the Go fix command on the packages named by the import paths.
+// Fix runs the Go fix tool (cmd/vet) on the named packages
+// and applies suggested fixes.
//
-// The -fix flag sets a comma-separated list of fixes to run.
-// The default is all known fixes.
-// (Its value is passed to 'go tool fix -r'.)
+// It supports these flags:
+//
+// -diff
+// instead of applying each fix, print the patch as a unified diff
+//
+// The -fixtool=prog flag selects a different analysis tool with
+// alternative or additional fixes; see the documentation for go vet's
+// -vettool flag for details.
//
-// For more about fix, see 'go doc cmd/fix'.
// For more about specifying packages, see 'go help packages'.
//
-// To run fix with other options, run 'go tool fix'.
+// For a list of fixers and their flags, see 'go tool fix help'.
+//
+// For details of a specific fixer such as 'hostport',
+// see 'go tool fix help hostport'.
+//
+// The build flags supported by go fix are those that control package resolution
+// and execution, such as -C, -n, -x, -v, -tags, and -toolexec.
+// For more about these flags, see 'go help build'.
//
// See also: go fmt, go vet.
//
//
// go vet [build flags] [-vettool prog] [vet flags] [packages]
//
-// Vet runs the Go vet command on the packages named by the import paths.
+// Vet runs the Go vet tool (cmd/vet) on the named packages
+// and reports diagnostics.
//
-// 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'.
+// It supports these flags:
+//
+// -c int
+// display offending line with this many lines of context (default -1)
+// -json
+// emit JSON output
+// -fix
+// instead of printing each diagnostic, apply its first fix (if any)
+// -diff
+// instead of applying each fix, print the patch as a unified diff
//
-// 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:
+// 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)
//
+// Alternative vet tools should be built atop golang.org/x/tools/go/analysis/unitchecker,
+// which handles the interaction with go 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 build flags supported by go vet are those that control package resolution
// and execution, such as -C, -n, -x, -v, -tags, and -toolexec.
// For more about these flags, see 'go help build'.
+++ /dev/null
-// 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 fix implements the “go fix” command.
-package fix
-
-import (
- "cmd/go/internal/base"
- "cmd/go/internal/cfg"
- "cmd/go/internal/load"
- "cmd/go/internal/modload"
- "cmd/go/internal/str"
- "cmd/go/internal/work"
- "context"
- "fmt"
- "go/build"
- "os"
- "path/filepath"
-)
-
-var CmdFix = &base.Command{
- UsageLine: "go fix [-fix list] [packages]",
- Short: "update packages to use new APIs",
- Long: `
-Fix runs the Go fix command on the packages named by the import paths.
-
-The -fix flag sets a comma-separated list of fixes to run.
-The default is all known fixes.
-(Its value is passed to 'go tool fix -r'.)
-
-For more about fix, see 'go doc cmd/fix'.
-For more about specifying packages, see 'go help packages'.
-
-To run fix with other options, run 'go tool fix'.
-
-See also: go fmt, go vet.
- `,
-}
-
-var fixes = CmdFix.Flag.String("fix", "", "comma-separated list of fixes to apply")
-
-func init() {
- work.AddBuildFlags(CmdFix, work.OmitBuildOnlyFlags)
- CmdFix.Run = runFix // fix cycle
-}
-
-func runFix(ctx context.Context, cmd *base.Command, args []string) {
- pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args)
- w := 0
- for _, pkg := range pkgs {
- if pkg.Error != nil {
- base.Errorf("%v", pkg.Error)
- continue
- }
- pkgs[w] = pkg
- w++
- }
- pkgs = pkgs[:w]
-
- printed := false
- for _, pkg := range pkgs {
- if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
- if !printed {
- fmt.Fprintf(os.Stderr, "go: not fixing packages in dependency modules\n")
- printed = true
- }
- continue
- }
- // Use pkg.gofiles instead of pkg.Dir so that
- // the command only applies to this package,
- // not to packages in subdirectories.
- files := base.RelPaths(pkg.InternalAllGoFiles())
- goVersion := ""
- if pkg.Module != nil {
- goVersion = "go" + pkg.Module.GoVersion
- } else if pkg.Standard {
- goVersion = build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1]
- }
- var fixArg []string
- if *fixes != "" {
- fixArg = []string{"-r=" + *fixes}
- }
- base.Run(str.StringList(cfg.BuildToolexec, filepath.Join(cfg.GOROOTbin, "go"), "tool", "fix", "-go="+goVersion, fixArg, files))
- }
-}
work.BuildInit()
work.VetFlags = testVet.flags
work.VetExplicit = testVet.explicit
+ work.VetTool = base.Tool("vet")
pkgOpts := load.PackageOpts{ModResolveTests: true}
pkgs = load.PackagesAndErrors(ctx, pkgOpts, pkgArgs)
// 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 implements the “go vet” and “go fix” commands.
package vet
import (
"context"
+ "encoding/json"
+ "errors"
"fmt"
- "path/filepath"
+ "io"
+ "os"
+ "slices"
+ "strconv"
+ "strings"
+ "sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/work"
)
-// Break init loop.
-func init() {
- CmdVet.Run = runVet
-}
-
var CmdVet = &base.Command{
CustomFlags: true,
UsageLine: "go vet [build flags] [-vettool prog] [vet flags] [packages]",
Short: "report likely mistakes in packages",
Long: `
-Vet runs the Go vet command on the packages named by the import paths.
+Vet runs the Go vet tool (cmd/vet) on the named packages
+and reports diagnostics.
-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'.
+It supports these flags:
-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:
+ -c int
+ display offending line with this many lines of context (default -1)
+ -json
+ emit JSON output
+ -fix
+ instead of printing each diagnostic, apply its first fix (if any)
+ -diff
+ instead of applying each fix, print the patch as a unified diff
+
+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)
+Alternative vet tools should be built atop golang.org/x/tools/go/analysis/unitchecker,
+which handles the interaction with go 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 build flags supported by go vet are those that control package resolution
and execution, such as -C, -n, -x, -v, -tags, and -toolexec.
For more about these flags, see 'go help build'.
`,
}
-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.
+var CmdFix = &base.Command{
+ CustomFlags: true,
+ UsageLine: "go fix [build flags] [-fixtool prog] [fix flags] [packages]",
+ Short: "apply fixes suggested by static checkers",
+ Long: `
+Fix runs the Go fix tool (cmd/vet) on the named packages
+and applies suggested fixes.
+
+It supports these flags:
+
+ -diff
+ instead of applying each fix, print the patch as a unified diff
+
+The -fixtool=prog flag selects a different analysis tool with
+alternative or additional fixes; see the documentation for go vet's
+-vettool flag for details.
+
+For more about specifying packages, see 'go help packages'.
+
+For a list of fixers and their flags, see 'go tool fix help'.
+
+For details of a specific fixer such as 'hostport',
+see 'go tool fix help hostport'.
+
+The build flags supported by go fix are those that control package resolution
+and execution, such as -C, -n, -x, -v, -tags, and -toolexec.
+For more about these flags, see 'go help build'.
+
+See also: go fmt, go vet.
+ `,
+}
+
+func init() {
+ // avoid initialization cycle
+ CmdVet.Run = run
+ CmdFix.Run = run
+
+ addFlags(CmdVet)
+ addFlags(CmdFix)
+}
+
+var (
+ // "go vet -fix" causes fixes to be applied.
+ vetFixFlag = CmdVet.Flag.Bool("fix", false, "apply the first fix (if any) for each diagnostic")
+
+ // The "go fix -fix=name,..." flag is an obsolete flag formerly
+ // used to pass a list of names to the old "cmd/fix -r".
+ fixFixFlag = CmdFix.Flag.String("fix", "", "obsolete; no effect")
+)
+
+// run implements both "go vet" and "go fix".
+func run(ctx context.Context, cmd *base.Command, args []string) {
+ // Compute flags for the vet/fix tool (e.g. cmd/{vet,fix}).
+ toolFlags, pkgArgs := toolFlags(cmd, args)
+
+ // The vet/fix commands do custom flag processing;
+ // initialize workspaces after that.
+ modload.InitWorkfile()
if cfg.DebugTrace != "" {
var close func() error
defer span.Done()
work.BuildInit()
- work.VetFlags = vetFlags
- if len(vetFlags) > 0 {
- work.VetExplicit = true
+
+ // Flag theory:
+ //
+ // All flags supported by unitchecker are accepted by go {vet,fix}.
+ // Some arise from each analyzer in the tool (both to enable it
+ // and to configure it), whereas others [-V -c -diff -fix -flags -json]
+ // are core to unitchecker itself.
+ //
+ // Most are passed through to toolFlags, but not all:
+ // * -V and -flags are used by the handshake in the [toolFlags] function;
+ // * these old flags have no effect: [-all -source -tags -v]; and
+ // * the [-c -fix -diff -json] flags are handled specially
+ // as described below:
+ //
+ // command args tool args
+ // go vet => cmd/vet -json Parse stdout, print diagnostics to stderr.
+ // go vet -json => cmd/vet -json Pass stdout through.
+ // go vet -fix [-diff] => cmd/vet -fix [-diff] Pass stdout through.
+ // go fix [-diff] => cmd/fix -fix [-diff] Pass stdout through.
+ // go fix -json => cmd/fix -json Pass stdout through.
+ //
+ // Notes:
+ // * -diff requires "go vet -fix" or "go fix", and no -json.
+ // * -json output is the same in "vet" and "fix" modes,
+ // and describes both diagnostics and fixes (but does not apply them).
+ // * -c=n is supported by the unitchecker, but we reimplement it
+ // here (see printDiagnostics), and do not pass the flag through.
+
+ work.VetExplicit = len(toolFlags) > 0
+
+ if cmd.Name() == "fix" || *vetFixFlag {
+ // fix mode: 'go fix' or 'go vet -fix'
+ if jsonFlag {
+ if diffFlag {
+ base.Fatalf("-json and -diff cannot be used together")
+ }
+ } else {
+ toolFlags = append(toolFlags, "-fix")
+ if diffFlag {
+ toolFlags = append(toolFlags, "-diff")
+ }
+ }
+ if contextFlag != -1 {
+ base.Fatalf("-c flag cannot be used when applying fixes")
+ }
+ } else {
+ // vet mode: 'go vet' without -fix
+ if !jsonFlag {
+ // Post-process the JSON diagnostics on stdout and format
+ // it as "file:line: message" diagnostics on stderr.
+ // (JSON reliably frames diagnostics, fixes, and errors so
+ // that we don't have to parse stderr or interpret non-zero
+ // exit codes, and interacts better with the action cache.)
+ toolFlags = append(toolFlags, "-json")
+ work.VetHandleStdout = printJSONDiagnostics
+ }
+ if diffFlag {
+ base.Fatalf("go vet -diff flag requires -fix")
+ }
}
- if vetTool != "" {
- var err error
- work.VetTool, err = filepath.Abs(vetTool)
- if err != nil {
- base.Fatalf("%v", err)
+
+ // Implement legacy "go fix -fix=name,..." flag.
+ if *fixFixFlag != "" {
+ fmt.Fprintf(os.Stderr, "go %s: the -fix=%s flag is obsolete and has no effect", cmd.Name(), *fixFixFlag)
+
+ // The buildtag fixer is now implemented by cmd/fix.
+ if slices.Contains(strings.Split(*fixFixFlag, ","), "buildtag") {
+ fmt.Fprintf(os.Stderr, "go %s: to enable the buildtag check, use -buildtag", cmd.Name())
}
}
+ work.VetFlags = toolFlags
+
pkgOpts := load.PackageOpts{ModResolveTests: true}
pkgs := load.PackagesAndErrors(ctx, pkgOpts, pkgArgs)
load.CheckPackageErrors(pkgs)
if len(pkgs) == 0 {
- base.Fatalf("no packages to vet")
+ base.Fatalf("no packages to %s", cmd.Name())
}
b := work.NewBuilder("")
}
}()
- root := &work.Action{Mode: "go vet"}
+ // To avoid file corruption from duplicate application of
+ // fixes (in fix mode), and duplicate reporting of diagnostics
+ // (in vet mode), we must run the tool only once for each
+ // source file. We achieve that by running on ptest (below)
+ // instead of p.
+ //
+ // As a side benefit, this also allows analyzers to make
+ // "closed world" assumptions and report diagnostics (such as
+ // "this symbol is unused") that might be false if computed
+ // from just the primary package p, falsified by the
+ // additional declarations in test files.
+ //
+ // We needn't worry about intermediate test variants, as they
+ // will only be executed in VetxOnly mode, for facts but not
+ // diagnostics.
+
+ root := &work.Action{Mode: "go " + cmd.Name()}
for _, p := range pkgs {
_, ptest, pxtest, perr := load.TestPackagesFor(ctx, pkgOpts, p, nil)
if perr != nil {
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)
+ base.Errorf("go: can't %s %s: no Go files in %s", cmd.Name(), p.ImportPath, p.Dir)
continue
}
if len(ptest.GoFiles) > 0 || len(ptest.CgoFiles) > 0 {
+ // The test package includes all the files of primary package.
root.Deps = append(root.Deps, b.VetAction(work.ModeBuild, work.ModeBuild, ptest))
}
if pxtest != nil {
}
b.Do(ctx, root)
}
+
+// printJSONDiagnostics parses JSON (from the tool's stdout) and
+// prints it (to stderr) in "file:line: message" form.
+// It also ensures that we exit nonzero if there were diagnostics.
+func printJSONDiagnostics(r io.Reader) error {
+ stdout, err := io.ReadAll(r)
+ if err != nil {
+ return err
+ }
+ if len(stdout) > 0 {
+ // unitchecker emits a JSON map of the form:
+ // output maps Package ID -> Analyzer.Name -> (error | []Diagnostic);
+ var tree jsonTree
+ if err := json.Unmarshal([]byte(stdout), &tree); err != nil {
+ return fmt.Errorf("parsing JSON: %v", err)
+ }
+ for _, units := range tree {
+ for analyzer, msg := range units {
+ if msg[0] == '[' {
+ // []Diagnostic
+ var diags []jsonDiagnostic
+ if err := json.Unmarshal([]byte(msg), &diags); err != nil {
+ return fmt.Errorf("parsing JSON diagnostics: %v", err)
+ }
+ for _, diag := range diags {
+ base.SetExitStatus(1)
+ printJSONDiagnostic(analyzer, diag)
+ }
+ } else {
+ // error
+ var e jsonError
+ if err := json.Unmarshal([]byte(msg), &e); err != nil {
+ return fmt.Errorf("parsing JSON error: %v", err)
+ }
+
+ base.SetExitStatus(1)
+ return errors.New(e.Err)
+ }
+ }
+ }
+ }
+ return nil
+}
+
+var stderrMu sync.Mutex // serializes concurrent writes to stdout
+
+func printJSONDiagnostic(analyzer string, diag jsonDiagnostic) {
+ stderrMu.Lock()
+ defer stderrMu.Unlock()
+
+ type posn struct {
+ file string
+ line, col int
+ }
+ parsePosn := func(s string) (_ posn, _ bool) {
+ colon2 := strings.LastIndexByte(s, ':')
+ if colon2 < 0 {
+ return
+ }
+ colon1 := strings.LastIndexByte(s[:colon2], ':')
+ if colon1 < 0 {
+ return
+ }
+ line, err := strconv.Atoi(s[colon1+len(":") : colon2])
+ if err != nil {
+ return
+ }
+ col, err := strconv.Atoi(s[colon2+len(":"):])
+ if err != nil {
+ return
+ }
+ return posn{s[:colon1], line, col}, true
+ }
+
+ print := func(start, end, message string) {
+ if posn, ok := parsePosn(start); ok {
+ // The (*work.Shell).reportCmd method relativizes the
+ // prefix of each line of the subprocess's stdout;
+ // but filenames in JSON aren't at the start of the line,
+ // so we need to apply ShortPath here too.
+ fmt.Fprintf(os.Stderr, "%s:%d:%d: %v\n", base.ShortPath(posn.file), posn.line, posn.col, message)
+ } else {
+ fmt.Fprintf(os.Stderr, "%s: %v\n", start, message)
+ }
+
+ // -c=n: show offending line plus N lines of context.
+ // (Duplicates logic in unitchecker; see analysisflags.PrintPlain.)
+ if contextFlag >= 0 {
+ if end == "" {
+ end = start
+ }
+ var (
+ startPosn, ok1 = parsePosn(start)
+ endPosn, ok2 = parsePosn(end)
+ )
+ if ok1 && ok2 {
+ // TODO(adonovan): respect overlays (like unitchecker does).
+ data, _ := os.ReadFile(startPosn.file)
+ lines := strings.Split(string(data), "\n")
+ for i := startPosn.line - contextFlag; i <= endPosn.line+contextFlag; i++ {
+ if 1 <= i && i <= len(lines) {
+ fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
+ }
+ }
+ }
+ }
+ }
+
+ // TODO(adonovan): append " [analyzer]" to message. But we must first relax
+ // x/tools/go/analysis/internal/versiontest.TestVettool and revendor; sigh.
+ _ = analyzer
+ print(diag.Posn, diag.End, diag.Message)
+ for _, rel := range diag.Related {
+ print(rel.Posn, rel.End, "\t"+rel.Message)
+ }
+}
+
+// -- JSON schema --
+
+// (populated by golang.org/x/tools/go/analysis/internal/analysisflags/flags.go)
+
+// A jsonTree is a mapping from package ID to analysis name to result.
+// Each result is either a jsonError or a list of jsonDiagnostic.
+type jsonTree map[string]map[string]json.RawMessage
+
+type jsonError struct {
+ Err string `json:"error"`
+}
+
+// A TextEdit describes the replacement of a portion of a file.
+// Start and End are zero-based half-open indices into the original byte
+// sequence of the file, and New is the new text.
+type jsonTextEdit struct {
+ Filename string `json:"filename"`
+ Start int `json:"start"`
+ End int `json:"end"`
+ New string `json:"new"`
+}
+
+// A jsonSuggestedFix describes an edit that should be applied as a whole or not
+// at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
+// consists of multiple non-contiguous edits.
+type jsonSuggestedFix struct {
+ Message string `json:"message"`
+ Edits []jsonTextEdit `json:"edits"`
+}
+
+// A jsonDiagnostic describes the json schema of an analysis.Diagnostic.
+type jsonDiagnostic struct {
+ Category string `json:"category,omitempty"`
+ Posn string `json:"posn"` // e.g. "file.go:line:column"
+ End string `json:"end"`
+ Message string `json:"message"`
+ SuggestedFixes []jsonSuggestedFix `json:"suggested_fixes,omitempty"`
+ Related []jsonRelatedInformation `json:"related,omitempty"`
+}
+
+// A jsonRelated describes a secondary position and message related to
+// a primary diagnostic.
+type jsonRelatedInformation struct {
+ Posn string `json:"posn"` // e.g. "file.go:line:column"
+ End string `json:"end"`
+ Message string `json:"message"`
+}
"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() {
- // For now, we omit the -json flag for vet because we could plausibly
- // support -json specific to the vet command in the future (perhaps using
- // the same format as build -json).
- work.AddBuildFlags(CmdVet, work.OmitJSONFlag)
- CmdVet.Flag.StringVar(&vetTool, "vettool", "", "")
+// go vet/fix flag processing
+var (
+ // We query the flags of the tool specified by -{vet,fix}tool
+ // 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.
+
+ // toolFlag specifies the vet/fix command to run.
+ // Any toolFlag that supports the (unpublished) vet
+ // command-line protocol may be supplied; see
+ // golang.org/x/tools/go/analysis/unitchecker for the
+ // sole implementation. It is also used by tests.
+ //
+ // The default behavior ("") runs 'go tool {vet,fix}'.
+ //
+ // Do not access this flag directly; use [parseToolFlag].
+ toolFlag string // -{vet,fix}tool
+ diffFlag bool // -diff
+ jsonFlag bool // -json
+ contextFlag = -1 // -c=n
+)
+
+func addFlags(cmd *base.Command) {
+ // We run the compiler for export data.
+ // Suppress the build -json flag; we define our own.
+ work.AddBuildFlags(cmd, work.OmitJSONFlag)
+
+ cmd.Flag.StringVar(&toolFlag, cmd.Name()+"tool", "", "") // -vettool or -fixtool
+ cmd.Flag.BoolVar(&diffFlag, "diff", false, "print diff instead of applying it")
+ cmd.Flag.BoolVar(&jsonFlag, "json", false, "print diagnostics and fixes as JSON")
+ cmd.Flag.IntVar(&contextFlag, "c", -1, "display offending line with this many lines of context")
}
-func parseVettoolFlag(args []string) {
- // Extract -vettool by ad hoc flag processing:
+// parseToolFlag scans args for -{vet,fix}tool and returns the effective tool filename.
+func parseToolFlag(cmd *base.Command, args []string) string {
+ toolFlagName := cmd.Name() + "tool" // vettool or fixtool
+
+ // Extract -{vet,fix}tool 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 arg == "-"+toolFlagName || arg == "--"+toolFlagName {
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
+ toolFlag = args[i+1]
+ break
+ } else if strings.HasPrefix(arg, "-"+toolFlagName+"=") ||
+ strings.HasPrefix(arg, "--"+toolFlagName+"=") {
+ toolFlag = arg[strings.IndexByte(arg, '=')+1:]
+ break
}
}
-}
-// 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 toolFlag != "" {
+ tool, err := filepath.Abs(toolFlag)
if err != nil {
log.Fatal(err)
}
+ return tool
}
+
+ return base.Tool(cmd.Name()) // default to 'go tool vet|fix'
+}
+
+// toolFlags processes the command line, splitting it at the first non-flag
+// into the list of flags and list of packages.
+func toolFlags(cmd *base.Command, args []string) (passToTool, packageNames []string) {
+ tool := parseToolFlag(cmd, args)
+ work.VetTool = tool
+
+ // Query the tool for its flags.
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)
+ toolcmd := exec.Command(tool, "-flags")
+ toolcmd.Stdout = out
+ if err := toolcmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "go: %s -flags failed: %v\n", tool, err)
base.SetExitStatus(2)
base.Exit()
}
base.Exit()
}
- // Add vet's flags to CmdVet.Flag.
+ // Add tool's flags to cmd.Flag.
//
- // Some flags, in particular -tags and -v, are known to vet but
+ // Some flags, in particular -tags and -v, are known to the tool 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
+ // However some, like -x, are known to the build but not to the tool.
+ isToolFlag := make(map[string]bool, len(analysisFlags))
+ cf := cmd.Flag
for _, f := range analysisFlags {
- isVetFlag[f.Name] = true
+ // We reimplement the unitchecker's -c=n flag.
+ // Don't allow it to be passed through.
+ if f.Name == "c" {
+ continue
+ }
+ isToolFlag[f.Name] = true
if cf.Lookup(f.Name) == nil {
if f.Bool {
cf.Bool(f.Name, false, "")
}
}
- // 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)
+ // Record the set of tool flags set by GOFLAGS. We want to pass them to
+ // the tool, but only if they aren't overridden by an explicit argument.
+ base.SetFromGOFLAGS(&cmd.Flag)
addFromGOFLAGS := map[string]bool{}
- CmdVet.Flag.Visit(func(f *flag.Flag) {
- if isVetFlag[f.Name] {
+ cmd.Flag.Visit(func(f *flag.Flag) {
+ if isToolFlag[f.Name] {
addFromGOFLAGS[f.Name] = true
}
})
explicitFlags := make([]string, 0, len(args))
for len(args) > 0 {
- f, remainingArgs, err := cmdflag.ParseOne(&CmdVet.Flag, args)
+ f, remainingArgs, err := cmdflag.ParseOne(&cmd.Flag, args)
if errors.Is(err, flag.ErrHelp) {
- exitWithUsage()
+ exitWithUsage(cmd)
}
if errors.Is(err, cmdflag.ErrFlagTerminator) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
- exitWithUsage()
+ exitWithUsage(cmd)
}
- if isVetFlag[f.Name] {
+ if isToolFlag[f.Name] {
// Forward the raw arguments rather than cleaned equivalents, just in
- // case the vet tool parses them idiosyncratically.
+ // case the tool parses them idiosyncratically.
explicitFlags = append(explicitFlags, args[:len(args)-len(remainingArgs)]...)
// This flag has been overridden explicitly, so don't forward its implicit
}
// Prepend arguments from GOFLAGS before other arguments.
- CmdVet.Flag.Visit(func(f *flag.Flag) {
+ cmd.Flag.Visit(func(f *flag.Flag) {
if addFromGOFLAGS[f.Name] {
- passToVet = append(passToVet, fmt.Sprintf("-%s=%s", f.Name, f.Value))
+ passToTool = append(passToTool, fmt.Sprintf("-%s=%s", f.Name, f.Value))
}
})
- passToVet = append(passToVet, explicitFlags...)
- return passToVet, packageNames
+ passToTool = append(passToTool, explicitFlags...)
+ return passToTool, 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())
+func exitWithUsage(cmd *base.Command) {
+ fmt.Fprintf(os.Stderr, "usage: %s\n", cmd.UsageLine)
+ fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", cmd.LongName())
// This part is additional to what (*Command).Usage does:
- cmd := "go tool vet"
- if vetTool != "" {
- cmd = vetTool
+ tool := toolFlag
+ if tool == "" {
+ tool = "go tool " + cmd.Name()
}
- 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)
+ fmt.Fprintf(os.Stderr, "Run '%s help' for a full list of flags and analyzers.\n", tool)
+ fmt.Fprintf(os.Stderr, "Run '%s -help' for an overview.\n", tool)
base.SetExitStatus(2)
base.Exit()
path := base.Tool(name)
desc := "go tool " + name
- // Special case: undocumented -vettool overrides usual vet,
- // for testing vet or supplying an alternative analysis tool.
- if name == "vet" && VetTool != "" {
+ // Special case: -{vet,fix}tool overrides usual cmd/{vet,fix}
+ // for testing or supplying an alternative analysis tool.
+ // (We use only "vet" terminology in the action graph.)
+ if name == "vet" {
path = VetTool
desc = VetTool
}
}
}
-// VetTool is the path to an alternate vet tool binary.
+// VetTool is the path to the effective vet or fix tool binary.
+// The user may specify a non-default value using -{vet,fix}tool.
// The caller is expected to set it (if needed) before executing any vet actions.
var VetTool string
// The caller is expected to set them before executing any vet actions.
var VetFlags []string
-// VetExplicit records whether the vet flags were set explicitly on the command line.
+// VetHandleStdout determines how the stdout output of each vet tool
+// invocation should be handled. The default behavior is to copy it to
+// the go command's stdout, atomically.
+var VetHandleStdout = copyToStdout
+
+// VetExplicit records whether the vet flags (which may include
+// -{vet,fix}tool) were set explicitly on the command line.
var VetExplicit bool
func (b *Builder) vet(ctx context.Context, a *Action) error {
sh := b.Shell(a)
+ // We use "vet" terminology even when building action graphs for go fix.
vcfg.VetxOnly = a.VetxOnly
vcfg.VetxOutput = a.Objdir + "vet.out"
vcfg.Stdout = a.Objdir + "vet.stdout"
// dependency tree turn on *more* analysis, as here.
// (The unsafeptr check does not write any facts for use by
// later vet runs, nor does unreachable.)
- if a.Package.Goroot && !VetExplicit && VetTool == "" {
+ if a.Package.Goroot && !VetExplicit && VetTool == base.Tool("vet") {
// Turn off -unsafeptr checks.
// There's too much unsafe.Pointer code
// that vet doesn't like in low-level packages
vcfg.PackageVetx[a1.Package.ImportPath] = a1.built
}
}
- key := cache.ActionID(h.Sum())
+ vetxKey := cache.ActionID(h.Sum()) // for .vetx file
+
+ fmt.Fprintf(h, "stdout\n")
+ stdoutKey := cache.ActionID(h.Sum()) // for .stdout file
- if vcfg.VetxOnly && !cfg.BuildA {
+ // Check the cache; -a forces a rebuild.
+ if !cfg.BuildA {
c := cache.Default()
- if file, _, err := cache.GetFile(c, key); err == nil {
- a.built = file
- return nil
+ if vcfg.VetxOnly {
+ if file, _, err := cache.GetFile(c, vetxKey); err == nil {
+ a.built = file
+ return nil
+ }
+ } else {
+ // Copy cached vet.std files to stdout.
+ if file, _, err := cache.GetFile(c, stdoutKey); err == nil {
+ f, err := os.Open(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close() // ignore error (can't fail)
+ return VetHandleStdout(f)
+ }
}
}
p := a.Package
tool := VetTool
if tool == "" {
- tool = base.Tool("vet")
+ panic("VetTool unset")
+ }
+
+ if err := sh.run(p.Dir, p.ImportPath, env, cfg.BuildToolexec, tool, vetFlags, a.Objdir+"vet.cfg"); err != nil {
+ return err
}
- runErr := sh.run(p.Dir, p.ImportPath, env, cfg.BuildToolexec, tool, vetFlags, a.Objdir+"vet.cfg")
- // If vet wrote export data, save it for input to future vets.
+ // Vet tool succeeded, possibly with facts and JSON stdout. Save both in cache.
+
+ // Save facts
if f, err := os.Open(vcfg.VetxOutput); err == nil {
+ defer f.Close() // ignore error
a.built = vcfg.VetxOutput
- cache.Default().Put(key, f) // ignore error
- f.Close() // ignore error
+ cache.Default().Put(vetxKey, f) // ignore error
}
- // If vet wrote to stdout, copy it to go's stdout, atomically.
+ // Save stdout.
if f, err := os.Open(vcfg.Stdout); err == nil {
- stdoutMu.Lock()
- if _, err := io.Copy(os.Stdout, f); err != nil && runErr == nil {
- runErr = fmt.Errorf("copying vet tool stdout: %w", err)
+ defer f.Close() // ignore error
+ if err := VetHandleStdout(f); err != nil {
+ return err
}
- f.Close() // ignore error
- stdoutMu.Unlock()
+ f.Seek(0, io.SeekStart) // ignore error
+ cache.Default().Put(stdoutKey, f) // ignore error
}
- return runErr
+ return nil
}
-var stdoutMu sync.Mutex // serializes concurrent writes (e.g. JSON values) to stdout
+var stdoutMu sync.Mutex // serializes concurrent writes (of e.g. JSON values) to stdout
+
+// copyToStdout copies the stream to stdout while holding the lock.
+func copyToStdout(r io.Reader) error {
+ stdoutMu.Lock()
+ defer stdoutMu.Unlock()
+ if _, err := io.Copy(os.Stdout, r); err != nil {
+ return fmt.Errorf("copying vet tool stdout: %w", err)
+ }
+ return nil
+}
// linkActionID computes the action ID for a link action.
func (b *Builder) linkActionID(a *Action) cache.ActionID {
"cmd/go/internal/clean"
"cmd/go/internal/doc"
"cmd/go/internal/envcmd"
- "cmd/go/internal/fix"
"cmd/go/internal/fmtcmd"
"cmd/go/internal/generate"
"cmd/go/internal/help"
clean.CmdClean,
doc.CmdDoc,
envcmd.CmdEnv,
- fix.CmdFix,
+ vet.CmdFix,
fmtcmd.CmdFmt,
generate.CmdGenerate,
modget.CmdGet,
go vet -C ../strings -n
stderr strings_test
+# go fix
+go fix -C ../strings -n
+stderr strings_test
+
# -C must be first on command line (as of Go 1.21)
! go test -n -C ../strings
stderr '^invalid value "../strings" for flag -C: -C flag must be first flag on command line$'
-env GO111MODULE=off
-
# Issue 27665. Verify that "go vet" analyzes non-Go files.
-[!GOARCH:amd64] skip
+env GO111MODULE=off
+env GOARCH=amd64
+
! go vet -asmdecl a
stderr 'f: invalid MOVW of x'
-# -c flag shows context
+# -c=n flag shows n lines of context
! go vet -c=2 -asmdecl a
stderr '...invalid MOVW...'
stderr '1 .*TEXT'
--- /dev/null
+# Test basic features of "go vet"/"go fix" CLI.
+#
+# The example relies on two analyzers:
+# - hostport (which is included in both the fix and vet suites), and
+# - printf (which is only in the vet suite).
+# Each reports one diagnostic with a fix.
+
+# vet default flags print diagnostics to stderr. Diagnostic => nonzero exit.
+! go vet example.com/x
+stderr 'does not work with IPv6'
+stderr 'non-constant format string in call to fmt.Sprintf'
+
+# -hostport runs only one analyzer. Diagnostic => failure.
+! go vet -hostport example.com/x
+stderr 'does not work with IPv6'
+! stderr 'non-constant format string'
+
+# -timeformat runs only one analyzer. No diagnostics => success.
+go vet -timeformat example.com/x
+! stderr .
+
+# JSON output includes diagnostics and fixes. Always success.
+go vet -json example.com/x
+! stderr .
+stdout '"example.com/x": {'
+stdout '"hostport":'
+stdout '"message": "address format .* does not work with IPv6",'
+stdout '"suggested_fixes":'
+stdout '"message": "Replace fmt.Sprintf with net.JoinHostPort",'
+
+# vet -fix -diff displays a diff.
+go vet -fix -diff example.com/x
+stdout '\-var _ = fmt.Sprintf\(s\)'
+stdout '\+var _ = fmt.Sprintf\("%s", s\)'
+stdout '\-var _, _ = net.Dial\("tcp", fmt.Sprintf\("%s:%d", s, 80\)\)'
+stdout '\+var _, _ = net.Dial\("tcp", net.JoinHostPort\(s, "80"\)\)'
+
+# vet -fix quietly applies the vet suite fixes.
+cp x.go x.go.bak
+go vet -fix example.com/x
+grep 'fmt.Sprintf\("%s", s\)' x.go
+grep 'net.JoinHostPort' x.go
+! stderr .
+cp x.go.bak x.go
+
+! go vet -diff example.com/x
+stderr 'go vet -diff flag requires -fix'
+
+# go fix applies the fix suite fixes.
+go fix example.com/x
+grep 'net.JoinHostPort' x.go
+! grep 'fmt.Sprintf\("%s", s\)' x.go
+! stderr .
+cp x.go.bak x.go
+
+# Show diff of fixes from the fix suite.
+go fix -diff example.com/x
+! stdout '\-var _ = fmt.Sprintf\(s\)'
+stdout '\-var _, _ = net.Dial\("tcp", fmt.Sprintf\("%s:%d", s, 80\)\)'
+stdout '\+var _, _ = net.Dial\("tcp", net.JoinHostPort\(s, "80"\)\)'
+
+# Show fix-suite fixes in JSON form.
+go fix -json example.com/x
+! stderr .
+stdout '"example.com/x": {'
+stdout '"hostport":'
+stdout '"message": "address format .* does not work with IPv6",'
+stdout '"suggested_fixes":'
+stdout '"message": "Replace fmt.Sprintf with net.JoinHostPort",'
+! stdout '"printf":'
+! stdout '"message": "non-constant format string.*",'
+! stdout '"message": "Insert.*%s.*format.string",'
+
+# Show vet-suite fixes in JSON form.
+go vet -fix -json example.com/x
+! stderr .
+stdout '"example.com/x": {'
+stdout '"hostport":'
+stdout '"message": "address format .* does not work with IPv6",'
+stdout '"suggested_fixes":'
+stdout '"message": "Replace fmt.Sprintf with net.JoinHostPort",'
+stdout '"printf":'
+stdout '"message": "non-constant format string.*",'
+stdout '"suggested_fixes":'
+stdout '"message": "Insert.*%s.*format.string",'
+
+# Reject -diff + -json.
+! go fix -diff -json example.com/x
+stderr '-json and -diff cannot be used together'
+
+# Legacy way of selecting fixers is a no-op.
+go fix -fix=old1,old2 example.com/x
+stderr 'go fix: the -fix=old1,old2 flag is obsolete and has no effect'
+
+# -c=n flag shows n lines of context.
+! go vet -c=2 -printf example.com/x
+stderr 'x.go:12:21: non-constant format string in call to fmt.Sprintf'
+! stderr '9 '
+stderr '10 '
+stderr '11 // This call...'
+stderr '12 var _ = fmt.Sprintf\(s\)'
+stderr '13 '
+stderr '14 '
+! stderr '15 '
+
+-- go.mod --
+module example.com/x
+go 1.25
+
+-- x.go --
+package x
+
+
+import (
+ "fmt"
+ "net"
+)
+
+var s string
+
+// This call yields a "non-constant format string" diagnostic, with a fix (go vet only).
+var _ = fmt.Sprintf(s)
+
+// This call yields a hostport diagnostic, with a fix (go vet and go fix).
+var _, _ = net.Dial("tcp", fmt.Sprintf("%s:%d", s, 80))
--- /dev/null
+# Test that go vet's caching of vet tool actions replays
+# the recorded stderr output even after a cache hit.
+
+# Set up fresh GOCACHE.
+env GOCACHE=$WORK/gocache
+
+# First time is a cache miss.
+! go vet example.com/a
+stderr 'fmt.Sprint call has possible Printf formatting directive'
+
+# Second time is assumed to be a cache hit for the stdout JSON,
+# but we don't bother to assert it. Same diagnostics again.
+! go vet example.com/a
+stderr 'fmt.Sprint call has possible Printf formatting directive'
+
+-- go.mod --
+module example.com
+
+-- a/a.go --
+package a
+
+import "fmt"
+
+var _ = fmt.Sprint("%s") // oops!
directive check Go toolchain directives such as //go:debug
errorsas report passing non-pointer or non-error values to errors.As
framepointer report assembly that clobbers the frame pointer before saving it
+ hostport check format of addresses passed to net.Dial
httpresponse check for mistakes using HTTP responses
ifaceassert detect impossible interface-to-interface type assertions
loopclosure check references to loop variables from within nested functions
sigchanyzer check for unbuffered channel of os.Signal
slog check for invalid structured logging calls
stdmethods check signature of methods of well-known interfaces
+ stdversion report uses of too-new standard library symbols
stringintconv check for string(int) conversions
structtag check that struct field tags conform to reflect.StructTag.Get
testinggoroutine report calls to (*testing.T).Fatal from goroutines started by a test
import (
"cmd/internal/objabi"
"cmd/internal/telemetry/counter"
- "flag"
-
- "golang.org/x/tools/go/analysis/unitchecker"
+ "golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/appends"
"golang.org/x/tools/go/analysis/passes/asmdecl"
"golang.org/x/tools/go/analysis/passes/assign"
"golang.org/x/tools/go/analysis/passes/unsafeptr"
"golang.org/x/tools/go/analysis/passes/unusedresult"
"golang.org/x/tools/go/analysis/passes/waitgroup"
+ "golang.org/x/tools/go/analysis/unitchecker"
)
func main() {
+ // Keep consistent with cmd/fix/main.go!
counter.Open()
objabi.AddVersionFlag()
-
counter.Inc("vet/invocations")
- unitchecker.Main(
- appends.Analyzer,
- asmdecl.Analyzer,
- assign.Analyzer,
- atomic.Analyzer,
- bools.Analyzer,
- buildtag.Analyzer,
- cgocall.Analyzer,
- composite.Analyzer,
- copylock.Analyzer,
- defers.Analyzer,
- directive.Analyzer,
- errorsas.Analyzer,
- framepointer.Analyzer,
- httpresponse.Analyzer,
- hostport.Analyzer,
- ifaceassert.Analyzer,
- loopclosure.Analyzer,
- lostcancel.Analyzer,
- nilfunc.Analyzer,
- printf.Analyzer,
- shift.Analyzer,
- sigchanyzer.Analyzer,
- slog.Analyzer,
- stdmethods.Analyzer,
- stdversion.Analyzer,
- stringintconv.Analyzer,
- structtag.Analyzer,
- tests.Analyzer,
- testinggoroutine.Analyzer,
- timeformat.Analyzer,
- unmarshal.Analyzer,
- unreachable.Analyzer,
- unsafeptr.Analyzer,
- unusedresult.Analyzer,
- waitgroup.Analyzer,
- )
- // It's possible that unitchecker will exit early. In
- // those cases the flags won't be counted.
- counter.CountFlags("vet/flag:", *flag.CommandLine)
+ unitchecker.Main(suite...) // (never returns)
+}
+
+// The vet suite analyzers report diagnostics.
+// (Diagnostics must describe real problems, but need not
+// suggest fixes, and fixes are not necessarily safe to apply.)
+var suite = []*analysis.Analyzer{
+ appends.Analyzer,
+ asmdecl.Analyzer,
+ assign.Analyzer,
+ atomic.Analyzer,
+ bools.Analyzer,
+ buildtag.Analyzer,
+ cgocall.Analyzer,
+ composite.Analyzer,
+ copylock.Analyzer,
+ defers.Analyzer,
+ directive.Analyzer,
+ errorsas.Analyzer,
+ // fieldalignment.Analyzer omitted: too noisy
+ framepointer.Analyzer,
+ httpresponse.Analyzer,
+ hostport.Analyzer,
+ ifaceassert.Analyzer,
+ loopclosure.Analyzer,
+ lostcancel.Analyzer,
+ nilfunc.Analyzer,
+ printf.Analyzer,
+ // shadow.Analyzer omitted: too noisy
+ shift.Analyzer,
+ sigchanyzer.Analyzer,
+ slog.Analyzer,
+ stdmethods.Analyzer,
+ stdversion.Analyzer,
+ stringintconv.Analyzer,
+ structtag.Analyzer,
+ tests.Analyzer,
+ testinggoroutine.Analyzer,
+ timeformat.Analyzer,
+ unmarshal.Analyzer,
+ unreachable.Analyzer,
+ unsafeptr.Analyzer,
+ unusedresult.Analyzer,
+ waitgroup.Analyzer,
}
Printf("hi") // ok
const format = "%s %s\n"
Printf(format, "hi", "there")
- Printf(format, "hi") // ERROR "Printf format %s reads arg #2, but call has 1 arg$"
+ Printf(format, "hi") // ERROR "Printf format %s reads arg #2, but call has 1 arg"
Printf("%s %d %.3v %q", "str", 4) // ERROR "Printf format %.3v reads arg #3, but call has 2 args"
f := new(ptrStringer)
f.Warn(0, "%s", "hello", 3) // ERROR "Warn call has possible Printf formatting directive %s"
os.Exit(0)
}
- os.Setenv("GO_VETTEST_IS_VET", "1") // Set for subprocesses to inherit.
+ // Set for subprocesses to inherit.
+ os.Setenv("GO_VETTEST_IS_VET", "1") // ignore error
os.Exit(m.Run())
}
cmd.Env = append(os.Environ(), "GOWORK=off")
cmd.Dir = "testdata/rangeloop"
cmd.Stderr = new(strings.Builder) // all vet output goes to stderr
- cmd.Run()
+ cmd.Run() // ignore error
stderr := cmd.Stderr.(fmt.Stringer).String()
filename := filepath.FromSlash("testdata/rangeloop/rangeloop.go")
if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
t.Errorf("error check failed: %s", err)
- t.Log("vet stderr:\n", cmd.Stderr)
+ t.Logf("vet stderr:\n<<%s>>", cmd.Stderr)
}
})
cmd.Env = append(os.Environ(), "GOWORK=off")
cmd.Dir = "testdata/stdversion"
cmd.Stderr = new(strings.Builder) // all vet output goes to stderr
- cmd.Run()
+ cmd.Run() // ignore error
stderr := cmd.Stderr.(fmt.Stringer).String()
filename := filepath.FromSlash("testdata/stdversion/stdversion.go")
if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
t.Errorf("error check failed: %s", err)
- t.Log("vet stderr:\n", cmd.Stderr)
+ t.Logf("vet stderr:\n<<%s>>", cmd.Stderr)
}
})
}
func errchk(c *exec.Cmd, files []string, t *testing.T) {
output, err := c.CombinedOutput()
if _, ok := err.(*exec.ExitError); !ok {
- t.Logf("vet output:\n%s", output)
+ t.Logf("vet output:\n<<%s>>", output)
t.Fatal(err)
}
fullshort := make([]string, 0, len(files)*2)
"x testtag y": 1,
"othertag": 2,
} {
- tag, wantFile := tag, wantFile
t.Run(tag, func(t *testing.T) {
t.Parallel()
t.Logf("-tags=%s", tag)
errmsgs, out = partitionStrings(we.prefix, out)
}
if len(errmsgs) == 0 {
- errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
+ errs = append(errs, fmt.Errorf("%s:%d: missing error %q (prefix: %s)", we.file, we.lineNum, we.reStr, we.prefix))
continue
}
matched := false