diff --git a/go/analysis/cmd/vet-lite/main.go b/go/analysis/cmd/vet-lite/main.go index e847d8df..d767d566 100644 --- a/go/analysis/cmd/vet-lite/main.go +++ b/go/analysis/cmd/vet-lite/main.go @@ -8,14 +8,8 @@ package main import ( "flag" - "fmt" - "log" - "os" - "strings" - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/internal/analysisflags" - "golang.org/x/tools/go/analysis/internal/unitchecker" + "golang.org/x/tools/go/analysis/unitchecker" "golang.org/x/tools/go/analysis/passes/asmdecl" "golang.org/x/tools/go/analysis/passes/assign" @@ -41,85 +35,55 @@ import ( "golang.org/x/tools/go/analysis/passes/unusedresult" ) -var analyzers = []*analysis.Analyzer{ - asmdecl.Analyzer, - assign.Analyzer, - atomic.Analyzer, - bools.Analyzer, - buildtag.Analyzer, - cgocall.Analyzer, - composite.Analyzer, - copylock.Analyzer, - httpresponse.Analyzer, - loopclosure.Analyzer, - lostcancel.Analyzer, - nilfunc.Analyzer, - pkgfact.Analyzer, - printf.Analyzer, - shift.Analyzer, - stdmethods.Analyzer, - structtag.Analyzer, - tests.Analyzer, - unmarshal.Analyzer, - unreachable.Analyzer, - unsafeptr.Analyzer, - unusedresult.Analyzer, +// Flags for legacy vet compatibility. +// +// These flags, plus the shims in analysisflags, enable +// existing scripts that run vet to continue to work. +// +// Legacy vet had the concept of "experimental" checkers. There +// was exactly one, shadow, and it had to be explicitly enabled +// by the -shadow flag, which would of course disable all the +// other tristate flags, requiring the -all flag to reenable them. +// (By itself, -all did not enable all checkers.) +// The -all flag is no longer needed, so it is a no-op. +// +// The shadow analyzer has been removed from the suite, +// but can be run using these additional commands: +// $ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow +// $ go vet -vettool=$(which shadow) +// Alternatively, one could build a multichecker containing all +// the desired checks (vet's suite + shadow) and run it in a +// single "go vet" command. +func init() { + _ = flag.Bool("source", false, "no effect (deprecated)") + _ = flag.Bool("v", false, "no effect (deprecated)") + _ = flag.Bool("all", false, "no effect (deprecated)") + _ = flag.String("tags", "", "no effect (deprecated)") } func main() { - log.SetFlags(0) - log.SetPrefix("vet: ") - - if err := analysis.Validate(analyzers); err != nil { - log.Fatal(err) - } - - // Flags for legacy vet compatibility. - // - // These flags, plus the shims in analysisflags, enable - // existing scripts that run vet to continue to work. - // - // Legacy vet had the concept of "experimental" checkers. There - // was exactly one, shadow, and it had to be explicitly enabled - // by the -shadow flag, which would of course disable all the - // other tristate flags, requiring the -all flag to reenable them. - // (By itself, -all did not enable all checkers.) - // The -all flag is no longer needed, so it is a no-op. - // - // The shadow analyzer has been removed from the suite, - // but can be run using these additional commands: - // $ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow - // $ go vet -vettool=$(which shadow) - // Alternatively, one could build a multichecker containing all - // the desired checks (vet's suite + shadow) and run it in a - // single "go vet" command. - for _, name := range []string{"source", "v", "all"} { - _ = flag.Bool(name, false, "no effect (deprecated)") - } - _ = flag.String("tags", "", "no effect (deprecated)") - - flag.Usage = func() { - fmt.Fprintln(os.Stderr, `Usage of vet: - vet unit.cfg # execute analysis specified by config file - vet help # general help - vet help name # help on specific analyzer and its flags`) - flag.PrintDefaults() - os.Exit(1) - } - - analyzers = analysisflags.Parse(analyzers, true) - - args := flag.Args() - if len(args) == 0 { - flag.Usage() - } - if args[0] == "help" { - analysisflags.Help("vet", analyzers, args[1:]) - os.Exit(0) - } - if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") { - log.Fatalf("invalid command: want .cfg file (this reduced version of vet is intended to be run only by the 'go vet' command)") - } - - unitchecker.Main(args[0], analyzers) + unitchecker.Main( + asmdecl.Analyzer, + assign.Analyzer, + atomic.Analyzer, + bools.Analyzer, + buildtag.Analyzer, + cgocall.Analyzer, + composite.Analyzer, + copylock.Analyzer, + httpresponse.Analyzer, + loopclosure.Analyzer, + lostcancel.Analyzer, + nilfunc.Analyzer, + pkgfact.Analyzer, + printf.Analyzer, + shift.Analyzer, + stdmethods.Analyzer, + structtag.Analyzer, + tests.Analyzer, + unmarshal.Analyzer, + unreachable.Analyzer, + unsafeptr.Analyzer, + unusedresult.Analyzer, + ) } diff --git a/go/analysis/internal/analysisflags/help.go b/go/analysis/internal/analysisflags/help.go index dc7ba066..66aa6245 100644 --- a/go/analysis/internal/analysisflags/help.go +++ b/go/analysis/internal/analysisflags/help.go @@ -3,39 +3,28 @@ package analysisflags import ( "flag" "fmt" - "io" "log" - "os" - "path/filepath" "sort" "strings" "golang.org/x/tools/go/analysis" ) -const usage = `PROGNAME is a tool for static analysis of Go programs. +const help = `PROGNAME is a tool for static analysis of Go programs. -PROGNAME examines Go source code and reports suspicious constructs, such as Printf -calls whose arguments do not align with the format string. It uses heuristics -that do not guarantee all reports are genuine problems, but it can find errors -not caught by the compilers. - -Usage: PROGNAME [-flag] [package] +PROGNAME examines Go source code and reports suspicious constructs, +such as Printf calls whose arguments do not align with the format +string. It uses heuristics that do not guarantee all reports are +genuine problems, but it can find errors not caught by the compilers. ` -// PrintUsage prints the usage message to stderr. -func PrintUsage(out io.Writer) { - progname := filepath.Base(os.Args[0]) - fmt.Fprintln(out, strings.Replace(usage, "PROGNAME", progname, -1)) -} - // Help implements the help subcommand for a multichecker or vet-lite // style command. The optional args specify the analyzers to describe. // Help calls log.Fatal if no such analyzer exists. func Help(progname string, analyzers []*analysis.Analyzer, args []string) { // No args: show summary of all analyzers. if len(args) == 0 { - PrintUsage(os.Stdout) + fmt.Println(strings.Replace(help, "PROGNAME", progname, -1)) fmt.Println("Registered analyzers:") fmt.Println() sort.Slice(analyzers, func(i, j int) bool { diff --git a/go/analysis/internal/checker/checker.go b/go/analysis/internal/checker/checker.go index bf8bfdbc..80fa02fa 100644 --- a/go/analysis/internal/checker/checker.go +++ b/go/analysis/internal/checker/checker.go @@ -25,7 +25,6 @@ import ( "time" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/internal/unitchecker" "golang.org/x/tools/go/packages" ) @@ -108,18 +107,6 @@ func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) { }() } - // The undocumented protocol used by 'go vet' - // is that a vet-like tool must support: - // - // -flags describe flags in JSON - // -V=full describe executable for build caching - // foo.cfg perform separate modular analyze on the single - // unit described by a JSON config file foo.cfg. - if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") { - unitchecker.Main(args[0], analyzers) - panic("unreachable") - } - // Load the packages. if dbg('v') { log.SetPrefix("") diff --git a/go/analysis/multichecker/multichecker.go b/go/analysis/multichecker/multichecker.go index d3aa7061..3c62be58 100644 --- a/go/analysis/multichecker/multichecker.go +++ b/go/analysis/multichecker/multichecker.go @@ -13,10 +13,12 @@ import ( "log" "os" "path/filepath" + "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/internal/analysisflags" "golang.org/x/tools/go/analysis/internal/checker" + "golang.org/x/tools/go/analysis/unitchecker" ) func Main(analyzers ...*analysis.Analyzer) { @@ -34,10 +36,13 @@ func Main(analyzers ...*analysis.Analyzer) { args := flag.Args() if len(args) == 0 { - analysisflags.PrintUsage(os.Stderr) - fmt.Fprintf(os.Stderr, "Run '%[1]s help' for more detail,\n"+ - " or '%[1]s help name' for details and flags of a specific analyzer.\n", - progname) + fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs. + +Usage: %[1]s [-flag] [package] + +Run '%[1]s help' for more detail, + or '%[1]s help name' for details and flags of a specific analyzer. +`, progname) os.Exit(1) } @@ -46,5 +51,10 @@ func Main(analyzers ...*analysis.Analyzer) { os.Exit(0) } + if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") { + unitchecker.Run(args[0], analyzers) + panic("unreachable") + } + os.Exit(checker.Run(args, analyzers)) } diff --git a/go/analysis/singlechecker/singlechecker.go b/go/analysis/singlechecker/singlechecker.go index 0ea62eab..5194183d 100644 --- a/go/analysis/singlechecker/singlechecker.go +++ b/go/analysis/singlechecker/singlechecker.go @@ -33,6 +33,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/internal/analysisflags" "golang.org/x/tools/go/analysis/internal/checker" + "golang.org/x/tools/go/analysis/unitchecker" ) // Main is the main function for a checker command for a single analysis. @@ -67,5 +68,10 @@ func Main(a *analysis.Analyzer) { os.Exit(1) } + if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") { + unitchecker.Run(args[0], analyzers) + panic("unreachable") + } + os.Exit(checker.Run(args, analyzers)) } diff --git a/go/analysis/internal/unitchecker/testdata/src/a/a.go b/go/analysis/unitchecker/testdata/src/a/a.go similarity index 100% rename from go/analysis/internal/unitchecker/testdata/src/a/a.go rename to go/analysis/unitchecker/testdata/src/a/a.go diff --git a/go/analysis/internal/unitchecker/testdata/src/b/b.go b/go/analysis/unitchecker/testdata/src/b/b.go similarity index 100% rename from go/analysis/internal/unitchecker/testdata/src/b/b.go rename to go/analysis/unitchecker/testdata/src/b/b.go diff --git a/go/analysis/internal/unitchecker/unitchecker.go b/go/analysis/unitchecker/unitchecker.go similarity index 83% rename from go/analysis/internal/unitchecker/unitchecker.go rename to go/analysis/unitchecker/unitchecker.go index 8ca42ab6..edfca577 100644 --- a/go/analysis/internal/unitchecker/unitchecker.go +++ b/go/analysis/unitchecker/unitchecker.go @@ -30,6 +30,7 @@ package unitchecker import ( "encoding/gob" "encoding/json" + "flag" "fmt" "go/ast" "go/build" @@ -41,12 +42,14 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "sort" "strings" "sync" "time" "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/internal/analysisflags" "golang.org/x/tools/go/analysis/internal/facts" ) @@ -68,9 +71,56 @@ type Config struct { SucceedOnTypecheckFailure bool } -// Main reads the *.cfg file, runs the analysis, +// Main is the main function of a vet-like analysis tool that must be +// invoked by a build system to analyze a single package. +// +// The protocol required by 'go vet -vettool=...' is that the tool must support: +// +// -flags describe flags in JSON +// -V=full describe executable for build caching +// foo.cfg perform separate modular analyze on the single +// unit described by a JSON config file foo.cfg. +// +func Main(analyzers ...*analysis.Analyzer) { + progname := filepath.Base(os.Args[0]) + log.SetFlags(0) + log.SetPrefix(progname + ": ") + + if err := analysis.Validate(analyzers); err != nil { + log.Fatal(err) + } + + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs. + +Usage of %[1]s: + %.16[1]s unit.cfg # execute analysis specified by config file + %.16[1]s help # general help + %.16[1]s help name # help on specific analyzer and its flags +`, progname) + os.Exit(1) + } + + analyzers = analysisflags.Parse(analyzers, true) + + args := flag.Args() + if len(args) == 0 { + flag.Usage() + } + if args[0] == "help" { + analysisflags.Help(progname, analyzers, args[1:]) + os.Exit(0) + } + if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") { + log.Fatalf("invalid command: want .cfg file (this reduced version of %s is intended to be run only by the 'go vet' command)", progname) + } + Run(args[0], analyzers) +} + +// Run reads the *.cfg file, runs the analysis, // and calls os.Exit with an appropriate error code. -func Main(configFile string, analyzers []*analysis.Analyzer) { +// It assumes flags have already been set. +func Run(configFile string, analyzers []*analysis.Analyzer) { cfg, err := readConfig(configFile) if err != nil { log.Fatal(err) diff --git a/go/analysis/internal/unitchecker/unitchecker_test.go b/go/analysis/unitchecker/unitchecker_test.go similarity index 74% rename from go/analysis/internal/unitchecker/unitchecker_test.go rename to go/analysis/unitchecker/unitchecker_test.go index c8362bfc..3b8d2370 100644 --- a/go/analysis/internal/unitchecker/unitchecker_test.go +++ b/go/analysis/unitchecker/unitchecker_test.go @@ -12,19 +12,15 @@ package unitchecker_test import ( "flag" - "log" "os" "os/exec" "runtime" - "strings" "testing" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/go/analysis/internal/analysisflags" - "golang.org/x/tools/go/analysis/internal/unitchecker" "golang.org/x/tools/go/analysis/passes/findcall" "golang.org/x/tools/go/analysis/passes/printf" + "golang.org/x/tools/go/analysis/unitchecker" ) func TestMain(m *testing.M) { @@ -40,24 +36,10 @@ func TestMain(m *testing.M) { } func main() { - findcall.Analyzer.Flags.Set("name", "MyFunc123") - - var analyzers = []*analysis.Analyzer{ + unitchecker.Main( findcall.Analyzer, printf.Analyzer, - } - - if err := analysis.Validate(analyzers); err != nil { - log.Fatal(err) - } - analyzers = analysisflags.Parse(analyzers, true) - - args := flag.Args() - if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") { - log.Fatalf("invalid command: want .cfg file") - } - - unitchecker.Main(args[0], analyzers) + ) } // This is a very basic integration test of modular @@ -72,7 +54,7 @@ func TestIntegration(t *testing.T) { testdata := analysistest.TestData() - cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "b") + cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "-findcall.name=MyFunc123", "b") cmd.Env = append(os.Environ(), "UNITCHECKER_CHILD=1", "GOPATH="+testdata,