go/analysis: add command-line help
The format of Analyzer.Doc is now specified as a short title followed by a longer description. This allows us to build a nice self-documenting command-line interface. Much of the documentation in vet's doc.go and file-level comments can now be displayed to the user. Change-Id: I462343e97ac9b743284aaa3e06e7a81d11e9593f Reviewed-on: https://go-review.googlesource.com/138396 Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
7b71b077e1
commit
51aacb1402
|
@ -32,6 +32,8 @@ type Analyzer struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Doc is the documentation for the analyzer.
|
// Doc is the documentation for the analyzer.
|
||||||
|
// The part before the first "\n\n" is the title
|
||||||
|
// (no capital or period, max ~60 letters).
|
||||||
Doc string
|
Doc string
|
||||||
|
|
||||||
// Flags defines any flags accepted by the analyzer.
|
// Flags defines any flags accepted by the analyzer.
|
||||||
|
|
|
@ -5,12 +5,21 @@ package multichecker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/analysis/internal/checker"
|
"golang.org/x/tools/go/analysis/internal/checker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const usage = `Analyze is a tool for static analysis of Go programs.
|
||||||
|
|
||||||
|
Usage: analyze [-flag] [package]
|
||||||
|
`
|
||||||
|
|
||||||
func Main(analyzers ...*analysis.Analyzer) {
|
func Main(analyzers ...*analysis.Analyzer) {
|
||||||
if err := analysis.Validate(analyzers); err != nil {
|
if err := analysis.Validate(analyzers); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -47,7 +56,86 @@ func Main(analyzers ...*analysis.Analyzer) {
|
||||||
analyzers = keep
|
analyzers = keep
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checker.Run(flag.Args(), analyzers); err != nil {
|
args := flag.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, usage)
|
||||||
|
fmt.Fprintln(os.Stderr, `Run 'analyze help' for more detail,
|
||||||
|
or 'analyze help name' for details and flags of a specific analyzer.`)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[0] == "help" {
|
||||||
|
help(analyzers, args[1:])
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checker.Run(args, analyzers); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func help(analyzers []*analysis.Analyzer, args []string) {
|
||||||
|
// No args: show summary of all analyzers.
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Println(usage)
|
||||||
|
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.Println("\nTo see details and flags of a specific analyzer, run 'analyze help name'.")
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,12 +11,15 @@ import (
|
||||||
|
|
||||||
var Analyzer = &analysis.Analyzer{
|
var Analyzer = &analysis.Analyzer{
|
||||||
Name: "findcall",
|
Name: "findcall",
|
||||||
Doc: "find calls to a particular function",
|
Doc: `find calls to a particular function
|
||||||
|
|
||||||
|
The findcall analysis reports calls to functions or methods
|
||||||
|
of a particular name.`,
|
||||||
Run: findcall,
|
Run: findcall,
|
||||||
RunDespiteErrors: true,
|
RunDespiteErrors: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = "println" // --name flag
|
var name = "println" // -name flag
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Analyzer.Flags.StringVar(&name, "name", name, "name of the function to find")
|
Analyzer.Flags.StringVar(&name, "name", name, "name of the function to find")
|
||||||
|
|
|
@ -21,7 +21,10 @@ package singlechecker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/analysis/internal/checker"
|
"golang.org/x/tools/go/analysis/internal/checker"
|
||||||
|
@ -42,9 +45,26 @@ func Main(a *analysis.Analyzer) {
|
||||||
flag.Var(f.Value, f.Name, f.Usage)
|
flag.Var(f.Value, f.Name, f.Usage)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
flag.Usage = func() {
|
||||||
|
paras := strings.Split(a.Doc, "\n\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n\n", a.Name, paras[0])
|
||||||
|
fmt.Printf("Usage: %s [-flag] [package]\n\n", a.Name)
|
||||||
|
if len(paras) > 1 {
|
||||||
|
fmt.Println(strings.Join(paras[1:], "\n\n"))
|
||||||
|
}
|
||||||
|
fmt.Println("\nFlags:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := checker.Run(flag.Args(), []*analysis.Analyzer{a}); err != nil {
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checker.Run(args, []*analysis.Analyzer{a}); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue