diff --git a/cmd/gocritic/main.go b/cmd/gocritic/main.go index 40a7c6657..b469c89c2 100644 --- a/cmd/gocritic/main.go +++ b/cmd/gocritic/main.go @@ -5,7 +5,7 @@ import ( "github.com/go-critic/go-critic/framework/lintmain" ) -var Version string +var Version = "v0.0.0-SNAPSHOT" func main() { lintmain.Run(lintmain.Config{ diff --git a/framework/cmdutil/cmdutil.go b/framework/cmdutil/cmdutil.go deleted file mode 100644 index 6ca583290..000000000 --- a/framework/cmdutil/cmdutil.go +++ /dev/null @@ -1,72 +0,0 @@ -package cmdutil - -import ( - "log" - "os" -) - -// SubCommand is an implementation of a linter sub-command. -type SubCommand struct { - // Main is command entry point. - Main func() - - // Name is sub-command name used to execute it. - Name string - - // Short describes command in one line of text. - Short string - - // Examples shows one or more sub-command execution examples. - Examples []string -} - -// DispatchCommand runs sub command out of specified cmdList based on -// the first command line argument. -func DispatchCommand(cmdList []*SubCommand) { - argv := os.Args - if len(argv) < 2 { - log.Printf("not enough arguments, expected sub-command name\n\n") - printSubCommands(cmdList) - os.Exit(1) - } - - subIdx := 1 // [0] is program name - sub := os.Args[subIdx] - // Erase sub-command argument (index=1) to make it invisible for - // sub commands themselves. - os.Args = append(os.Args[:subIdx], os.Args[subIdx+1:]...) - - // Choose and run sub-command main. - cmd := findSubCommand(cmdList, sub) - if cmd == nil { - log.Printf("unknown sub-command: %s\n\n", sub) - printSubCommands(cmdList) - os.Exit(1) - } else { - // The called function may exit with non-zero status. - // No code should follow this call. - cmd.Main() - } -} - -// findSubCommand looks up SubCommand by its name. -// Returns nil if requested command not found. -func findSubCommand(cmdList []*SubCommand, name string) *SubCommand { - for _, cmd := range cmdList { - if cmd.Name == name { - return cmd - } - } - return nil -} - -// printSubCommands prints cmdList info to the logger (usually stderr). -func printSubCommands(cmdList []*SubCommand) { - log.Println("Supported sub-commands:") - for _, cmd := range cmdList { - log.Printf("\t%s - %s", cmd.Name, cmd.Short) - for _, ex := range cmd.Examples { - log.Printf("\t\t$ %s", ex) - } - } -} diff --git a/framework/lintmain/internal/check/check.go b/framework/lintmain/internal/check/check.go index f49bfc571..20d9443f9 100644 --- a/framework/lintmain/internal/check/check.go +++ b/framework/lintmain/internal/check/check.go @@ -2,6 +2,7 @@ package check import ( "bytes" + "context" "errors" "flag" "fmt" @@ -27,8 +28,9 @@ import ( ) // Main implements sub-command entry point. -func Main() { +func Main(_ context.Context, args []string) error { var p program + p.flagSet = flag.NewFlagSet("gocritic", flag.ContinueOnError) p.infoList = linter.GetCheckersInfo() steps := []struct { @@ -38,7 +40,7 @@ func Main() { {"load plugin", p.loadPlugin}, {"bind checker params", p.bindCheckerParams}, {"bind default enabled list", p.bindDefaultEnabledList}, - {"parse args", p.parseArgs}, + {"parse args", func() error { return p.parseArgs(args) }}, {"start profiling", p.startProfiling}, {"assign checker params", p.assignCheckerParams}, {"load program", p.loadProgram}, @@ -53,11 +55,14 @@ func Main() { log.Fatalf("%s: %v", step.name, err) } } + return nil } type program struct { ctx *linter.Context + flagSet *flag.FlagSet + fset *token.FileSet loadedPackages []*packages.Package @@ -313,11 +318,11 @@ func (p *program) bindCheckerParams() error { key := p.checkerParamKey(info, pname) switch v := param.Value.(type) { case int: - intParams[key] = flag.Int(key, v, param.Usage) + intParams[key] = p.flagSet.Int(key, v, param.Usage) case bool: - boolParams[key] = flag.Bool(key, v, param.Usage) + boolParams[key] = p.flagSet.Bool(key, v, param.Usage) case string: - stringParams[key] = flag.String(key, v, param.Usage) + stringParams[key] = p.flagSet.String(key, v, param.Usage) default: panic("unreachable") // Checked in AddChecker } @@ -351,32 +356,34 @@ func (p *program) bindDefaultEnabledList() error { return nil } -func (p *program) parseArgs() error { - flag.BoolVar(&p.filters.enableAll, "enableAll", false, +func (p *program) parseArgs(args []string) error { + p.flagSet.BoolVar(&p.filters.enableAll, "enableAll", false, `identical to -enable with all checkers listed. If true, -enable is ignored`) - enable := flag.String("enable", strings.Join(p.filters.defaultCheckers, ","), + enable := p.flagSet.String("enable", strings.Join(p.filters.defaultCheckers, ","), `comma-separated list of enabled checkers. Can include #tags`) - disable := flag.String("disable", "", + disable := p.flagSet.String("disable", "", `comma-separated list of checkers to be disabled. Can include #tags`) - flag.IntVar(&p.exitCode, "exitCode", 1, + p.flagSet.IntVar(&p.exitCode, "exitCode", 1, `exit code to be used when lint issues are found`) - flag.BoolVar(&p.checkTests, "checkTests", true, + p.flagSet.BoolVar(&p.checkTests, "checkTests", true, `whether to check test files`) - flag.BoolVar(&p.shorterErrLocation, `shorterErrLocation`, true, + p.flagSet.BoolVar(&p.shorterErrLocation, `shorterErrLocation`, true, `whether to replace error location prefix with $GOROOT and $GOPATH`) - flag.BoolVar(&p.verbose, "v", false, + p.flagSet.BoolVar(&p.verbose, "v", false, `whether to print output useful during linter debugging`) - flag.StringVar(&p.goVersion, "go", "", + p.flagSet.StringVar(&p.goVersion, "go", "", `select the Go version to target. Leave as string for the latest`) - flag.StringVar(&p.memProfile, "memprofile", "", + p.flagSet.StringVar(&p.memProfile, "memprofile", "", `write memory profile to the specified file`) - flag.StringVar(&p.cpuProfile, "cpuprofile", "", + p.flagSet.StringVar(&p.cpuProfile, "cpuprofile", "", `write CPU profile to the specified file`) - flag.Parse() + if err := p.flagSet.Parse(args); err != nil { + return err + } - p.packages = flag.Args() + p.packages = p.flagSet.Args() p.filters.enable = strings.Split(*enable, ",") p.filters.disable = strings.Split(*disable, ",") diff --git a/framework/lintmain/internal/lintdoc/doc.go b/framework/lintmain/internal/lintdoc/doc.go index 331d3e568..460911445 100644 --- a/framework/lintmain/internal/lintdoc/doc.go +++ b/framework/lintmain/internal/lintdoc/doc.go @@ -1,6 +1,7 @@ package lintdoc import ( + "context" "flag" "fmt" "log" @@ -12,10 +13,13 @@ import ( ) // Main implements sub-command entry point. -func Main() { - flag.Parse() +func Main(_ context.Context, args []string) error { + flagSet := flag.NewFlagSet("gocritic", flag.ContinueOnError) + if err := flagSet.Parse(args); err != nil { + return err + } - switch args := flag.Args(); len(args) { + switch args := flagSet.Args(); len(args) { case 0: printShortDoc() case 1: @@ -23,6 +27,7 @@ func Main() { default: log.Fatalf("expected 0 or 1 positional arguments") } + return nil } func printShortDoc() { diff --git a/framework/lintmain/lintmain.go b/framework/lintmain/lintmain.go index 77834f77f..706ef97bb 100644 --- a/framework/lintmain/lintmain.go +++ b/framework/lintmain/lintmain.go @@ -1,12 +1,12 @@ package lintmain import ( - "fmt" "log" - "github.com/go-critic/go-critic/framework/cmdutil" "github.com/go-critic/go-critic/framework/lintmain/internal/check" "github.com/go-critic/go-critic/framework/lintmain/internal/lintdoc" + + "github.com/cristalhq/acmd" ) // Config is used to parametrize the linter. @@ -20,44 +20,24 @@ type Config struct { func Run(cfg Config) { log.SetFlags(0) - printVersion := func() { - log.Println(cfg.Version) - } - - // makeExample replaces all ${linter} placeholders to a bound linter name. - makeExamples := func(examples ...string) []string { - for i := range examples { - examples[i] = fmt.Sprintf(examples[i], cfg.Name) - } - return examples - } - - subCommands := []*cmdutil.SubCommand{ - { - Main: check.Main, - Name: "check", - Short: "run linter over specified targets", - Examples: makeExamples( - "%s check -help", - "%s check -enable='paramTypeCombine,unslice' strings bytes", - "%s check -v -enable='#diagnostic' -disable='#experimental,#opinionated' ./..."), - }, - { - Main: printVersion, - Name: "version", - Short: "print linter version", - Examples: makeExamples("%s version"), - }, - { - Main: lintdoc.Main, - Name: "doc", - Short: "get installed checkers documentation", - Examples: makeExamples( - "%s doc -help", - "%s doc", - "%s doc checkerName"), - }, + r := acmd.RunnerOf(cmds, acmd.Config{ + AppName: cfg.Name, + Version: cfg.Version, + }) + if err := r.Run(); err != nil { + log.Print(err.Error()) } +} - cmdutil.DispatchCommand(subCommands) +var cmds = []acmd.Command{ + { + Name: "check", + Description: "run linter over specified targets", + Do: check.Main, + }, + { + Name: "doc", + Description: "get installed checkers documentation", + Do: lintdoc.Main, + }, } diff --git a/go.mod b/go.mod index 33d5838c0..4e1ae5874 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/go-critic/go-critic go 1.16 require ( + github.com/cristalhq/acmd v0.5.7 github.com/go-toolsmith/astcast v1.0.0 github.com/go-toolsmith/astcopy v1.0.0 github.com/go-toolsmith/astequal v1.0.1 diff --git a/go.sum b/go.sum index 963822a39..8c105bfb1 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cristalhq/acmd v0.5.7 h1:QBPleWdW8kVsn6VuiCNXhtbjvFuHlTvfPu7GR6J7EXQ= +github.com/cristalhq/acmd v0.5.7/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=