mirror of
https://github.com/golang/go
synced 2024-11-18 17:54:57 -07:00
go/analysis/cmd/vet-lite: make CLI closer to cmd/vet
Details: - Add -source, -v, and -all flags to vet-lite. These have no effect and issue a warning. - Add usage message to vet-lite that lists all analyzers and explains -foo.enable and other flags. - Factor this help message (common to vet-lite and multichecker) into analysisflags. - Add legacy aliases of new flags. e.g. -printfuncs is now -printf.funcs The old names work but issue a warning when used. Also: update comments to say -vettool not$GOVETTOOL I think we should probably do away with singlechecker in a follow-up: a singleton multichecker is good enough, and will allow us to remove cases in the flag-processing logic. Change-Id: Ib62f16b5e2f4c382a29e6300a6246b2db9e08049 Reviewed-on: https://go-review.googlesource.com/c/148559 Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
4e34152f16
commit
a00bb74625
@ -1,7 +1,7 @@
|
||||
// The vet-lite command is a driver for static checkers conforming to
|
||||
// the golang.org/x/tools/go/analysis API. It must be run by go vet:
|
||||
//
|
||||
// $ GOVETTOOL=$(which vet-lite) go vet
|
||||
// $ go vet -vettool=$(which vet-lite)
|
||||
//
|
||||
// For a checker also capable of running standalone, use multichecker.
|
||||
package main
|
||||
@ -9,6 +9,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
@ -74,6 +75,34 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Flags for legacy vet compatibility.
|
||||
//
|
||||
// These flags, plus the shims in analysisflags, enable all
|
||||
// existing scripts that run vet to continue to work.
|
||||
//
|
||||
// We still need to deal with legacy vet's "experimental"
|
||||
// checkers. In vet there is exactly one such checker, shadow,
|
||||
// and it must be enabled explicitly with the -shadow flag, but
|
||||
// of course setting it disables all the other tristate flags,
|
||||
// requiring the -all flag to reenable them.
|
||||
//
|
||||
// I don't believe this feature carries its weight. I propose we
|
||||
// simply skip shadow for now; the few users that want it can
|
||||
// run "go vet -vettool=..." using a vet tool that includes
|
||||
// shadow, either as an additional step, with a shadow
|
||||
// "singlechecker", or in place of the regular vet step on a
|
||||
// multichecker with a hand-picked suite of checkers.
|
||||
// Or, we could improve the shadow checker to the point where it
|
||||
// need not be experimental.
|
||||
for _, name := range []string{"source", "v", "all"} {
|
||||
flag.Var(warnBoolFlag(name), name, "no effect (deprecated)")
|
||||
}
|
||||
|
||||
flag.Usage = func() {
|
||||
analysisflags.Help("vet", analyzers, nil)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
analyzers = analysisflags.Parse(analyzers, true)
|
||||
|
||||
args := flag.Args()
|
||||
@ -83,3 +112,12 @@ func main() {
|
||||
|
||||
unitchecker.Main(args[0], analyzers)
|
||||
}
|
||||
|
||||
type warnBoolFlag string
|
||||
|
||||
func (f warnBoolFlag) Set(s string) error {
|
||||
log.Printf("warning: deprecated flag -%s has no effect", string(f))
|
||||
return nil
|
||||
}
|
||||
func (f warnBoolFlag) IsBoolFlag() bool { return true }
|
||||
func (f warnBoolFlag) String() string { return "false" }
|
||||
|
@ -69,6 +69,15 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
|
||||
printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
|
||||
addVersionFlag()
|
||||
|
||||
// Add shims for legacy vet flags.
|
||||
for old, new := range vetLegacyFlags {
|
||||
newFlag := flag.Lookup(new)
|
||||
if newFlag != nil && flag.Lookup(old) == nil {
|
||||
oldFlag := &warnFlag{old, new, newFlag.Value}
|
||||
flag.Var(oldFlag, old, "deprecated alias for -"+new)
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse() // (ExitOnError)
|
||||
|
||||
// -flags: print flags so that go vet knows which ones are legitimate.
|
||||
@ -221,3 +230,63 @@ func (ts *triState) String() string {
|
||||
func (ts triState) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Legacy flag support
|
||||
|
||||
// vetLegacyFlags maps flags used by legacy vet to their corresponding
|
||||
// new names. Uses of the old names will continue to work, but with a
|
||||
// log message. TODO(adonovan): delete this mechanism after Nov 2019.
|
||||
var vetLegacyFlags = map[string]string{
|
||||
"asmdecl": "asmdecl.enable",
|
||||
"assign": "assign.enable",
|
||||
"atomic": "atomic.enable",
|
||||
"bool": "bools.enable",
|
||||
"buildtags": "buildtag.enable",
|
||||
"cgocall": "cgocall.enable",
|
||||
"composites": "composites.enable",
|
||||
"compositewhitelist": "composites.whitelist",
|
||||
"copylocks": "copylocks.enable",
|
||||
"flags": "flags.enable",
|
||||
"httpresponse": "httpresponse.enable",
|
||||
"lostcancel": "lostcancel.enable",
|
||||
"methods": "stdmethods.enable",
|
||||
"nilfunc": "nilfunc.enable",
|
||||
"printf": "printf.enable",
|
||||
"printfuncs": "printf.funcs",
|
||||
"rangeloops": "loopclosure.enable",
|
||||
"shadow": "shadow.enable",
|
||||
"shadowstrict": "shadow.strict",
|
||||
"shift": "shift.enable",
|
||||
"source": "source.enable",
|
||||
"structtags": "structtag.enable",
|
||||
"tests": "tests.enable",
|
||||
// "unmarshal": "unmarshal.enable", // TODO(adonovan) a new checker
|
||||
"unreachable": "unreachable.enable",
|
||||
"unsafeptr": "unsafeptr.enable",
|
||||
"unusedresult": "unusedresult.enable",
|
||||
"unusedfuncs": "unusedresult.funcs",
|
||||
"unusedstringmethods": "unusedresult.stringmethods",
|
||||
}
|
||||
|
||||
type warnFlag struct {
|
||||
old, new string
|
||||
flag.Value
|
||||
}
|
||||
|
||||
func (f *warnFlag) Set(s string) error {
|
||||
log.Printf("warning: deprecated flag -%s; use -%s instead", f.old, f.new)
|
||||
return f.Value.Set(s)
|
||||
}
|
||||
|
||||
func (f *warnFlag) IsBoolFlag() bool {
|
||||
b, ok := f.Value.(interface{ IsBoolFlag() bool })
|
||||
return ok && b.IsBoolFlag()
|
||||
}
|
||||
|
||||
func (f *warnFlag) String() string {
|
||||
// The flag package may call new(warnFlag).String.
|
||||
if f.Value == nil {
|
||||
return ""
|
||||
}
|
||||
return f.Value.String()
|
||||
}
|
||||
|
100
go/analysis/internal/analysisflags/help.go
Normal file
100
go/analysis/internal/analysisflags/help.go
Normal file
@ -0,0 +1,100 @@
|
||||
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.
|
||||
|
||||
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]
|
||||
`
|
||||
|
||||
// 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("Registered analyzers:")
|
||||
fmt.Println()
|
||||
sort.Slice(analyzers, func(i, j int) bool {
|
||||
return analyzers[i].Name < analyzers[j].Name
|
||||
})
|
||||
for _, a := range analyzers {
|
||||
title := strings.Split(a.Doc, "\n\n")[0]
|
||||
fmt.Printf(" %-12s %s\n", a.Name, title)
|
||||
}
|
||||
fmt.Println("\nBy default all analyzers are run.")
|
||||
fmt.Println("To select specific analyzers, use the -NAME.enable flag for each one,")
|
||||
fmt.Println(" or -NAME.enable=false to run all analyzers not explicitly disabled.")
|
||||
|
||||
// Show only the core command-line flags.
|
||||
fmt.Println("\nCore flags:")
|
||||
fmt.Println()
|
||||
fs := flag.NewFlagSet("", flag.ExitOnError)
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
if !strings.Contains(f.Name, ".") {
|
||||
fs.Var(f.Value, f.Name, f.Usage)
|
||||
}
|
||||
})
|
||||
fs.PrintDefaults()
|
||||
|
||||
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Show help on specific analyzer(s).
|
||||
outer:
|
||||
for _, arg := range args {
|
||||
for _, a := range analyzers {
|
||||
if a.Name == arg {
|
||||
paras := strings.Split(a.Doc, "\n\n")
|
||||
title := paras[0]
|
||||
fmt.Printf("%s: %s\n", a.Name, title)
|
||||
|
||||
// Show only the flags relating to this analysis,
|
||||
// properly prefixed.
|
||||
first := true
|
||||
fs := flag.NewFlagSet(a.Name, flag.ExitOnError)
|
||||
a.Flags.VisitAll(func(f *flag.Flag) {
|
||||
if first {
|
||||
first = false
|
||||
fmt.Println("\nAnalyzer flags:")
|
||||
fmt.Println()
|
||||
}
|
||||
fs.Var(f.Value, a.Name+"."+f.Name, f.Usage)
|
||||
})
|
||||
fs.PrintDefaults()
|
||||
|
||||
if len(paras) > 1 {
|
||||
fmt.Printf("\n%s\n", strings.Join(paras[1:], "\n\n"))
|
||||
}
|
||||
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
log.Fatalf("Analyzer %q not registered", arg)
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
// driver that analyzes a single compilation unit during a build.
|
||||
// It is invoked by a build system such as "go vet":
|
||||
//
|
||||
// $ GOVETTOOL=$(which vet) go vet
|
||||
// $ go vet -vettool=$(which vet)
|
||||
//
|
||||
// It supports the following command-line protocol:
|
||||
//
|
||||
|
@ -6,8 +6,9 @@
|
||||
|
||||
package unitchecker_test
|
||||
|
||||
// This test depends on go1.12 features such as go vet's support for
|
||||
// GOVETTOOL, and the (*os/exec.ExitError).ExitCode method.
|
||||
// This test depends on features such as
|
||||
// go vet's support for vetx files (1.11) and
|
||||
// the (*os.ProcessState).ExitCode method (1.12).
|
||||
|
||||
import (
|
||||
"flag"
|
||||
@ -71,9 +72,8 @@ func TestIntegration(t *testing.T) {
|
||||
|
||||
testdata := analysistest.TestData()
|
||||
|
||||
cmd := exec.Command("go", "vet", "b")
|
||||
cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "b")
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GOVETTOOL="+os.Args[0],
|
||||
"UNITCHECKER_CHILD=1",
|
||||
"GOPATH="+testdata,
|
||||
)
|
||||
|
@ -13,8 +13,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||
@ -28,20 +26,10 @@ import (
|
||||
// so it should be used as guidance only, not as a firm indicator of
|
||||
// program correctness."
|
||||
|
||||
const usage = `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]
|
||||
`
|
||||
|
||||
func Main(analyzers ...*analysis.Analyzer) {
|
||||
progname := filepath.Base(os.Args[0])
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix(filepath.Base(os.Args[0]) + ": ") // e.g. "vet: "
|
||||
log.SetPrefix(progname + ": ") // e.g. "vet: "
|
||||
|
||||
if err := analysis.Validate(analyzers); err != nil {
|
||||
log.Fatal(err)
|
||||
@ -53,7 +41,7 @@ func Main(analyzers ...*analysis.Analyzer) {
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(os.Stderr, strings.Replace(usage, "PROGNAME", progname, -1))
|
||||
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)
|
||||
@ -61,7 +49,7 @@ func Main(analyzers ...*analysis.Analyzer) {
|
||||
}
|
||||
|
||||
if args[0] == "help" {
|
||||
help(progname, analyzers, args[1:])
|
||||
analysisflags.Help(progname, analyzers, args[1:])
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@ -69,69 +57,3 @@ func Main(analyzers ...*analysis.Analyzer) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func help(progname string, analyzers []*analysis.Analyzer, args []string) {
|
||||
// No args: show summary of all analyzers.
|
||||
if len(args) == 0 {
|
||||
fmt.Println(strings.Replace(usage, "PROGNAME", progname, -1))
|
||||
fmt.Println("Registered analyzers:")
|
||||
fmt.Println()
|
||||
sort.Slice(analyzers, func(i, j int) bool {
|
||||
return analyzers[i].Name < analyzers[j].Name
|
||||
})
|
||||
for _, a := range analyzers {
|
||||
title := strings.Split(a.Doc, "\n\n")[0]
|
||||
fmt.Printf(" %-12s %s\n", a.Name, title)
|
||||
}
|
||||
fmt.Println("\nBy default all analyzers are run.")
|
||||
fmt.Println("To select specific analyzers, use the -NAME.enable flag for each one.")
|
||||
|
||||
// Show only the core command-line flags.
|
||||
fmt.Println("\nCore flags:")
|
||||
fmt.Println()
|
||||
fs := flag.NewFlagSet("", flag.ExitOnError)
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
if !strings.Contains(f.Name, ".") {
|
||||
fs.Var(f.Value, f.Name, f.Usage)
|
||||
}
|
||||
})
|
||||
fs.PrintDefaults()
|
||||
|
||||
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Show help on specific analyzer(s).
|
||||
outer:
|
||||
for _, arg := range args {
|
||||
for _, a := range analyzers {
|
||||
if a.Name == arg {
|
||||
paras := strings.Split(a.Doc, "\n\n")
|
||||
title := paras[0]
|
||||
fmt.Printf("%s: %s\n", a.Name, title)
|
||||
|
||||
// Show only the flags relating to this analysis,
|
||||
// properly prefixed.
|
||||
first := true
|
||||
fs := flag.NewFlagSet(a.Name, flag.ExitOnError)
|
||||
a.Flags.VisitAll(func(f *flag.Flag) {
|
||||
if first {
|
||||
first = false
|
||||
fmt.Println("\nAnalyzer flags:")
|
||||
fmt.Println()
|
||||
}
|
||||
fs.Var(f.Value, a.Name+"."+f.Name, f.Usage)
|
||||
})
|
||||
fs.PrintDefaults()
|
||||
|
||||
if len(paras) > 1 {
|
||||
fmt.Printf("\n%s\n", strings.Join(paras[1:], "\n\n"))
|
||||
}
|
||||
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
log.Fatalf("Analyzer %q not registered", arg)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user