go/analysis/unitchecker: a main function for vet-lite tools
(By "vet lite", we mean static tools that must be invoked by a build system, such as 'go vet'.) This CL publishes the former internal/unitchecker package. Its misnamed Main function is renamed Run, and it has a new Main that does the steps of a real main (log, flag, etc). The motivation for this change is to reduce cmd/vet-lite to the point of triviality to simplify the maintenance of the vendored copy of x/tools in GOROOT, because GOROOT/src/cmd/vet will need a copy of that logic. It is now essentially a one-liner. Also, improve usage messages; analysisflags.PrintUsage wasn't appropriate for all callers so it has been eliminated. Each of {single,multi,unit}checker prints its own 1-line usage message. Change-Id: I214c0e4ae7a2923eee8df3f7548341f2320cad2b Reviewed-on: https://go-review.googlesource.com/c/149742 Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
1fdeb1692e
commit
17409aa234
|
@ -8,14 +8,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis/unitchecker"
|
||||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
|
||||||
"golang.org/x/tools/go/analysis/internal/unitchecker"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis/passes/asmdecl"
|
"golang.org/x/tools/go/analysis/passes/asmdecl"
|
||||||
"golang.org/x/tools/go/analysis/passes/assign"
|
"golang.org/x/tools/go/analysis/passes/assign"
|
||||||
|
@ -41,85 +35,55 @@ import (
|
||||||
"golang.org/x/tools/go/analysis/passes/unusedresult"
|
"golang.org/x/tools/go/analysis/passes/unusedresult"
|
||||||
)
|
)
|
||||||
|
|
||||||
var analyzers = []*analysis.Analyzer{
|
// Flags for legacy vet compatibility.
|
||||||
asmdecl.Analyzer,
|
//
|
||||||
assign.Analyzer,
|
// These flags, plus the shims in analysisflags, enable
|
||||||
atomic.Analyzer,
|
// existing scripts that run vet to continue to work.
|
||||||
bools.Analyzer,
|
//
|
||||||
buildtag.Analyzer,
|
// Legacy vet had the concept of "experimental" checkers. There
|
||||||
cgocall.Analyzer,
|
// was exactly one, shadow, and it had to be explicitly enabled
|
||||||
composite.Analyzer,
|
// by the -shadow flag, which would of course disable all the
|
||||||
copylock.Analyzer,
|
// other tristate flags, requiring the -all flag to reenable them.
|
||||||
httpresponse.Analyzer,
|
// (By itself, -all did not enable all checkers.)
|
||||||
loopclosure.Analyzer,
|
// The -all flag is no longer needed, so it is a no-op.
|
||||||
lostcancel.Analyzer,
|
//
|
||||||
nilfunc.Analyzer,
|
// The shadow analyzer has been removed from the suite,
|
||||||
pkgfact.Analyzer,
|
// but can be run using these additional commands:
|
||||||
printf.Analyzer,
|
// $ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
|
||||||
shift.Analyzer,
|
// $ go vet -vettool=$(which shadow)
|
||||||
stdmethods.Analyzer,
|
// Alternatively, one could build a multichecker containing all
|
||||||
structtag.Analyzer,
|
// the desired checks (vet's suite + shadow) and run it in a
|
||||||
tests.Analyzer,
|
// single "go vet" command.
|
||||||
unmarshal.Analyzer,
|
func init() {
|
||||||
unreachable.Analyzer,
|
_ = flag.Bool("source", false, "no effect (deprecated)")
|
||||||
unsafeptr.Analyzer,
|
_ = flag.Bool("v", false, "no effect (deprecated)")
|
||||||
unusedresult.Analyzer,
|
_ = flag.Bool("all", false, "no effect (deprecated)")
|
||||||
|
_ = flag.String("tags", "", "no effect (deprecated)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
unitchecker.Main(
|
||||||
log.SetPrefix("vet: ")
|
asmdecl.Analyzer,
|
||||||
|
assign.Analyzer,
|
||||||
if err := analysis.Validate(analyzers); err != nil {
|
atomic.Analyzer,
|
||||||
log.Fatal(err)
|
bools.Analyzer,
|
||||||
}
|
buildtag.Analyzer,
|
||||||
|
cgocall.Analyzer,
|
||||||
// Flags for legacy vet compatibility.
|
composite.Analyzer,
|
||||||
//
|
copylock.Analyzer,
|
||||||
// These flags, plus the shims in analysisflags, enable
|
httpresponse.Analyzer,
|
||||||
// existing scripts that run vet to continue to work.
|
loopclosure.Analyzer,
|
||||||
//
|
lostcancel.Analyzer,
|
||||||
// Legacy vet had the concept of "experimental" checkers. There
|
nilfunc.Analyzer,
|
||||||
// was exactly one, shadow, and it had to be explicitly enabled
|
pkgfact.Analyzer,
|
||||||
// by the -shadow flag, which would of course disable all the
|
printf.Analyzer,
|
||||||
// other tristate flags, requiring the -all flag to reenable them.
|
shift.Analyzer,
|
||||||
// (By itself, -all did not enable all checkers.)
|
stdmethods.Analyzer,
|
||||||
// The -all flag is no longer needed, so it is a no-op.
|
structtag.Analyzer,
|
||||||
//
|
tests.Analyzer,
|
||||||
// The shadow analyzer has been removed from the suite,
|
unmarshal.Analyzer,
|
||||||
// but can be run using these additional commands:
|
unreachable.Analyzer,
|
||||||
// $ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
|
unsafeptr.Analyzer,
|
||||||
// $ go vet -vettool=$(which shadow)
|
unusedresult.Analyzer,
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,39 +3,28 @@ package analysisflags
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"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
|
PROGNAME examines Go source code and reports suspicious constructs,
|
||||||
calls whose arguments do not align with the format string. It uses heuristics
|
such as Printf calls whose arguments do not align with the format
|
||||||
that do not guarantee all reports are genuine problems, but it can find errors
|
string. It uses heuristics that do not guarantee all reports are
|
||||||
not caught by the compilers.
|
genuine problems, but it can find errors not caught by the compilers.
|
||||||
|
|
||||||
Usage: PROGNAME [-flag] [package]
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// 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
|
// Help implements the help subcommand for a multichecker or vet-lite
|
||||||
// style command. The optional args specify the analyzers to describe.
|
// style command. The optional args specify the analyzers to describe.
|
||||||
// Help calls log.Fatal if no such analyzer exists.
|
// Help calls log.Fatal if no such analyzer exists.
|
||||||
func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
|
func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
|
||||||
// No args: show summary of all analyzers.
|
// No args: show summary of all analyzers.
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
PrintUsage(os.Stdout)
|
fmt.Println(strings.Replace(help, "PROGNAME", progname, -1))
|
||||||
fmt.Println("Registered analyzers:")
|
fmt.Println("Registered analyzers:")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
sort.Slice(analyzers, func(i, j int) bool {
|
sort.Slice(analyzers, func(i, j int) bool {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/analysis/internal/unitchecker"
|
|
||||||
"golang.org/x/tools/go/packages"
|
"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.
|
// Load the packages.
|
||||||
if dbg('v') {
|
if dbg('v') {
|
||||||
log.SetPrefix("")
|
log.SetPrefix("")
|
||||||
|
|
|
@ -13,10 +13,12 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||||
"golang.org/x/tools/go/analysis/internal/checker"
|
"golang.org/x/tools/go/analysis/internal/checker"
|
||||||
|
"golang.org/x/tools/go/analysis/unitchecker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Main(analyzers ...*analysis.Analyzer) {
|
func Main(analyzers ...*analysis.Analyzer) {
|
||||||
|
@ -34,10 +36,13 @@ func Main(analyzers ...*analysis.Analyzer) {
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
analysisflags.PrintUsage(os.Stderr)
|
fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
|
||||||
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",
|
Usage: %[1]s [-flag] [package]
|
||||||
progname)
|
|
||||||
|
Run '%[1]s help' for more detail,
|
||||||
|
or '%[1]s help name' for details and flags of a specific analyzer.
|
||||||
|
`, progname)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,5 +51,10 @@ func Main(analyzers ...*analysis.Analyzer) {
|
||||||
os.Exit(0)
|
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))
|
os.Exit(checker.Run(args, analyzers))
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||||
"golang.org/x/tools/go/analysis/internal/checker"
|
"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.
|
// 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)
|
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))
|
os.Exit(checker.Run(args, analyzers))
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ package unitchecker
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
@ -41,12 +42,14 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||||
"golang.org/x/tools/go/analysis/internal/facts"
|
"golang.org/x/tools/go/analysis/internal/facts"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,9 +71,56 @@ type Config struct {
|
||||||
SucceedOnTypecheckFailure bool
|
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.
|
// 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)
|
cfg, err := readConfig(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
|
@ -12,19 +12,15 @@ package unitchecker_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
|
||||||
"golang.org/x/tools/go/analysis/analysistest"
|
"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/findcall"
|
||||||
"golang.org/x/tools/go/analysis/passes/printf"
|
"golang.org/x/tools/go/analysis/passes/printf"
|
||||||
|
"golang.org/x/tools/go/analysis/unitchecker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -40,24 +36,10 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
findcall.Analyzer.Flags.Set("name", "MyFunc123")
|
unitchecker.Main(
|
||||||
|
|
||||||
var analyzers = []*analysis.Analyzer{
|
|
||||||
findcall.Analyzer,
|
findcall.Analyzer,
|
||||||
printf.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
|
// This is a very basic integration test of modular
|
||||||
|
@ -72,7 +54,7 @@ func TestIntegration(t *testing.T) {
|
||||||
|
|
||||||
testdata := analysistest.TestData()
|
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(),
|
cmd.Env = append(os.Environ(),
|
||||||
"UNITCHECKER_CHILD=1",
|
"UNITCHECKER_CHILD=1",
|
||||||
"GOPATH="+testdata,
|
"GOPATH="+testdata,
|
Loading…
Reference in New Issue