mirror of
https://github.com/mgechev/revive.git
synced 2025-07-15 01:04:40 +02:00
Allow revive to be called with extra linters (#650)
This change allows revive to be called from main.go in other libraries and pass in a list of custom linters to be added to the built-in linters found in config Co-authored-by: Bernardo Heynemann <bernardo.heynemann@coinbase.com> Co-authored-by: chavacava <salvadorcavadini+github@gmail.com>
This commit is contained in:
committed by
GitHub
parent
5ce2ff53c0
commit
1c283837a9
281
cli/main.go
Normal file
281
cli/main.go
Normal file
@ -0,0 +1,281 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/mgechev/dots"
|
||||
"github.com/mgechev/revive/config"
|
||||
"github.com/mgechev/revive/lint"
|
||||
"github.com/mgechev/revive/logging"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
builtBy = "unknown"
|
||||
)
|
||||
|
||||
func fail(err string) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// ExtraRule configures a new rule to be used with revive.
|
||||
type ExtraRule struct {
|
||||
Rule lint.Rule
|
||||
DefaultConfig lint.RuleConfig
|
||||
}
|
||||
|
||||
// NewExtraRule returns a configured extra rule
|
||||
func NewExtraRule(rule lint.Rule, defaultConfig lint.RuleConfig) ExtraRule {
|
||||
return ExtraRule{
|
||||
Rule: rule,
|
||||
DefaultConfig: defaultConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// RunRevive runs the CLI for revive.
|
||||
func RunRevive(extraRules ...ExtraRule) {
|
||||
log, err := logging.GetLogger()
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
formatter, err := config.GetFormatter(formatterName)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
conf, err := config.GetConfig(configPath)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
if setExitStatus {
|
||||
conf.ErrorCode = 1
|
||||
conf.WarningCode = 1
|
||||
}
|
||||
|
||||
extraRuleInstances := make([]lint.Rule, len(extraRules))
|
||||
for i, extraRule := range extraRules {
|
||||
extraRuleInstances[i] = extraRule.Rule
|
||||
|
||||
ruleName := extraRule.Rule.Name()
|
||||
_, isRuleAlreadyConfigured := conf.Rules[ruleName]
|
||||
if !isRuleAlreadyConfigured {
|
||||
conf.Rules[ruleName] = extraRule.DefaultConfig
|
||||
}
|
||||
}
|
||||
|
||||
lintingRules, err := config.GetLintingRules(conf, extraRuleInstances)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
log.Println("Config loaded")
|
||||
|
||||
if len(excludePaths) == 0 { // if no excludes were set in the command line
|
||||
excludePaths = conf.Exclude // use those from the configuration
|
||||
}
|
||||
|
||||
packages, err := getPackages(excludePaths)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
revive := lint.New(func(file string) ([]byte, error) {
|
||||
return ioutil.ReadFile(file)
|
||||
}, maxOpenFiles)
|
||||
|
||||
failures, err := revive.Lint(packages, lintingRules, *conf)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
formatChan := make(chan lint.Failure)
|
||||
exitChan := make(chan bool)
|
||||
|
||||
var output string
|
||||
go (func() {
|
||||
output, err = formatter.Format(formatChan, *conf)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
exitChan <- true
|
||||
})()
|
||||
|
||||
exitCode := 0
|
||||
for f := range failures {
|
||||
if f.Confidence < conf.Confidence {
|
||||
continue
|
||||
}
|
||||
if exitCode == 0 {
|
||||
exitCode = conf.WarningCode
|
||||
}
|
||||
if c, ok := conf.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
|
||||
exitCode = conf.ErrorCode
|
||||
}
|
||||
if c, ok := conf.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
|
||||
exitCode = conf.ErrorCode
|
||||
}
|
||||
|
||||
formatChan <- f
|
||||
}
|
||||
|
||||
close(formatChan)
|
||||
<-exitChan
|
||||
if output != "" {
|
||||
fmt.Println(output)
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func normalizeSplit(strs []string) []string {
|
||||
res := []string{}
|
||||
for _, s := range strs {
|
||||
t := strings.Trim(s, " \t")
|
||||
if len(t) > 0 {
|
||||
res = append(res, t)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getPackages(excludePaths arrayFlags) ([][]string, error) {
|
||||
globs := normalizeSplit(flag.Args())
|
||||
if len(globs) == 0 {
|
||||
globs = append(globs, ".")
|
||||
}
|
||||
|
||||
packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
type arrayFlags []string
|
||||
|
||||
func (i *arrayFlags) String() string {
|
||||
return strings.Join([]string(*i), " ")
|
||||
}
|
||||
|
||||
func (i *arrayFlags) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
configPath string
|
||||
excludePaths arrayFlags
|
||||
formatterName string
|
||||
help bool
|
||||
versionFlag bool
|
||||
setExitStatus bool
|
||||
maxOpenFiles int
|
||||
)
|
||||
|
||||
var originalUsage = flag.Usage
|
||||
|
||||
func getLogo() string {
|
||||
return color.YellowString(` _ __ _____ _(_)__ _____
|
||||
| '__/ _ \ \ / / \ \ / / _ \
|
||||
| | | __/\ V /| |\ V / __/
|
||||
|_| \___| \_/ |_| \_/ \___|`)
|
||||
}
|
||||
|
||||
func getCall() string {
|
||||
return color.MagentaString("revive -config c.toml -formatter friendly -exclude a.go -exclude b.go ./...")
|
||||
}
|
||||
|
||||
func getBanner() string {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
Example:
|
||||
%s
|
||||
`, getLogo(), getCall())
|
||||
}
|
||||
|
||||
func buildDefaultConfigPath() string {
|
||||
var result string
|
||||
if homeDir, err := homedir.Dir(); err == nil {
|
||||
result = filepath.Join(homeDir, "revive.toml")
|
||||
if _, err := os.Stat(result); err != nil {
|
||||
result = ""
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Force colorizing for no TTY environments
|
||||
if os.Getenv("REVIVE_FORCE_COLOR") == "1" {
|
||||
color.NoColor = false
|
||||
}
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Println(getBanner())
|
||||
originalUsage()
|
||||
}
|
||||
|
||||
// command line help strings
|
||||
const (
|
||||
configUsage = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
|
||||
excludeUsage = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
|
||||
formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
|
||||
versionUsage = "get revive version"
|
||||
exitStatusUsage = "set exit status to 1 if any issues are found, overwrites errorCode and warningCode in config"
|
||||
maxOpenFilesUsage = "maximum number of open files at the same time"
|
||||
)
|
||||
|
||||
defaultConfigPath := buildDefaultConfigPath()
|
||||
|
||||
flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
|
||||
flag.Var(&excludePaths, "exclude", excludeUsage)
|
||||
flag.StringVar(&formatterName, "formatter", "", formatterUsage)
|
||||
flag.BoolVar(&versionFlag, "version", false, versionUsage)
|
||||
flag.BoolVar(&setExitStatus, "set_exit_status", false, exitStatusUsage)
|
||||
flag.IntVar(&maxOpenFiles, "max_open_files", 0, maxOpenFilesUsage)
|
||||
flag.Parse()
|
||||
|
||||
// Output build info (version, commit, date and builtBy)
|
||||
if versionFlag {
|
||||
var buildInfo string
|
||||
if date != "unknown" && builtBy != "unknown" {
|
||||
buildInfo = fmt.Sprintf("Built\t\t%s by %s\n", date, builtBy)
|
||||
}
|
||||
|
||||
if commit != "none" {
|
||||
buildInfo = fmt.Sprintf("Commit:\t\t%s\n%s", commit, buildInfo)
|
||||
}
|
||||
|
||||
if version == "dev" {
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if ok {
|
||||
version = bi.Main.Version
|
||||
if strings.HasPrefix(version, "v") {
|
||||
version = bi.Main.Version[1:]
|
||||
}
|
||||
if len(buildInfo) == 0 {
|
||||
fmt.Printf("version %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Version:\t%s\n%s", version, buildInfo)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
@ -107,11 +107,17 @@ func getFormatters() map[string]lint.Formatter {
|
||||
}
|
||||
|
||||
// GetLintingRules yields the linting rules that must be applied by the linter
|
||||
func GetLintingRules(config *lint.Config) ([]lint.Rule, error) {
|
||||
func GetLintingRules(config *lint.Config, extraRules []lint.Rule) ([]lint.Rule, error) {
|
||||
rulesMap := map[string]lint.Rule{}
|
||||
for _, r := range allRules {
|
||||
rulesMap[r.Name()] = r
|
||||
}
|
||||
for _, r := range extraRules {
|
||||
if _, ok := rulesMap[r.Name()]; ok {
|
||||
continue
|
||||
}
|
||||
rulesMap[r.Name()] = r
|
||||
}
|
||||
|
||||
var lintingRules []lint.Rule
|
||||
for name, ruleConfig := range config.Rules {
|
||||
|
@ -92,7 +92,7 @@ func TestGetLintingRules(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error while loading conf: %v", err)
|
||||
}
|
||||
rules, err := GetLintingRules(cfg)
|
||||
rules, err := GetLintingRules(cfg, []lint.Rule{})
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Fatalf("Unexpected error\n\t%v", err)
|
||||
@ -130,7 +130,7 @@ func TestGetGlobalSeverity(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error while loading conf: %v", err)
|
||||
}
|
||||
rules, err := GetLintingRules(cfg)
|
||||
rules, err := GetLintingRules(cfg, []lint.Rule{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error while loading conf: %v", err)
|
||||
}
|
||||
|
251
main.go
251
main.go
@ -1,254 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/mgechev/dots"
|
||||
"github.com/mgechev/revive/config"
|
||||
"github.com/mgechev/revive/lint"
|
||||
"github.com/mgechev/revive/logging"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
builtBy = "unknown"
|
||||
)
|
||||
|
||||
func fail(err string) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
import "github.com/mgechev/revive/cli"
|
||||
|
||||
func main() {
|
||||
log, err := logging.GetLogger()
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
conf, err := config.GetConfig(configPath)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
formatter, err := config.GetFormatter(formatterName)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
if setExitStatus {
|
||||
conf.ErrorCode = 1
|
||||
conf.WarningCode = 1
|
||||
}
|
||||
|
||||
if len(excludePaths) == 0 { // if no excludes were set in the command line
|
||||
excludePaths = conf.Exclude // use those from the configuration
|
||||
}
|
||||
|
||||
packages, err := getPackages(excludePaths)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
revive := lint.New(func(file string) ([]byte, error) {
|
||||
return ioutil.ReadFile(file)
|
||||
}, maxOpenFiles)
|
||||
|
||||
lintingRules, err := config.GetLintingRules(conf)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
log.Println("Config loaded")
|
||||
|
||||
failures, err := revive.Lint(packages, lintingRules, *conf)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
formatChan := make(chan lint.Failure)
|
||||
exitChan := make(chan bool)
|
||||
|
||||
var output string
|
||||
go (func() {
|
||||
output, err = formatter.Format(formatChan, *conf)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
exitChan <- true
|
||||
})()
|
||||
|
||||
exitCode := 0
|
||||
for f := range failures {
|
||||
if f.Confidence < conf.Confidence {
|
||||
continue
|
||||
}
|
||||
if exitCode == 0 {
|
||||
exitCode = conf.WarningCode
|
||||
}
|
||||
if c, ok := conf.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
|
||||
exitCode = conf.ErrorCode
|
||||
}
|
||||
if c, ok := conf.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
|
||||
exitCode = conf.ErrorCode
|
||||
}
|
||||
|
||||
formatChan <- f
|
||||
}
|
||||
|
||||
close(formatChan)
|
||||
<-exitChan
|
||||
if output != "" {
|
||||
fmt.Println(output)
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func normalizeSplit(strs []string) []string {
|
||||
res := []string{}
|
||||
for _, s := range strs {
|
||||
t := strings.Trim(s, " \t")
|
||||
if len(t) > 0 {
|
||||
res = append(res, t)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getPackages(excludePaths arrayFlags) ([][]string, error) {
|
||||
globs := normalizeSplit(flag.Args())
|
||||
if len(globs) == 0 {
|
||||
globs = append(globs, ".")
|
||||
}
|
||||
|
||||
packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
type arrayFlags []string
|
||||
|
||||
func (i *arrayFlags) String() string {
|
||||
return strings.Join([]string(*i), " ")
|
||||
}
|
||||
|
||||
func (i *arrayFlags) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
configPath string
|
||||
excludePaths arrayFlags
|
||||
formatterName string
|
||||
help bool
|
||||
versionFlag bool
|
||||
setExitStatus bool
|
||||
maxOpenFiles int
|
||||
)
|
||||
|
||||
var originalUsage = flag.Usage
|
||||
|
||||
func getLogo() string {
|
||||
return color.YellowString(` _ __ _____ _(_)__ _____
|
||||
| '__/ _ \ \ / / \ \ / / _ \
|
||||
| | | __/\ V /| |\ V / __/
|
||||
|_| \___| \_/ |_| \_/ \___|`)
|
||||
}
|
||||
|
||||
func getCall() string {
|
||||
return color.MagentaString("revive -config c.toml -formatter friendly -exclude a.go -exclude b.go ./...")
|
||||
}
|
||||
|
||||
func getBanner() string {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
Example:
|
||||
%s
|
||||
`, getLogo(), getCall())
|
||||
}
|
||||
|
||||
func buildDefaultConfigPath() string {
|
||||
var result string
|
||||
if homeDir, err := homedir.Dir(); err == nil {
|
||||
result = filepath.Join(homeDir, "revive.toml")
|
||||
if _, err := os.Stat(result); err != nil {
|
||||
result = ""
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Force colorizing for no TTY environments
|
||||
if os.Getenv("REVIVE_FORCE_COLOR") == "1" {
|
||||
color.NoColor = false
|
||||
}
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Println(getBanner())
|
||||
originalUsage()
|
||||
}
|
||||
|
||||
// command line help strings
|
||||
const (
|
||||
configUsage = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
|
||||
excludeUsage = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
|
||||
formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
|
||||
versionUsage = "get revive version"
|
||||
exitStatusUsage = "set exit status to 1 if any issues are found, overwrites errorCode and warningCode in config"
|
||||
maxOpenFilesUsage = "maximum number of open files at the same time"
|
||||
)
|
||||
|
||||
defaultConfigPath := buildDefaultConfigPath()
|
||||
|
||||
flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
|
||||
flag.Var(&excludePaths, "exclude", excludeUsage)
|
||||
flag.StringVar(&formatterName, "formatter", "", formatterUsage)
|
||||
flag.BoolVar(&versionFlag, "version", false, versionUsage)
|
||||
flag.BoolVar(&setExitStatus, "set_exit_status", false, exitStatusUsage)
|
||||
flag.IntVar(&maxOpenFiles, "max_open_files", 0, maxOpenFilesUsage)
|
||||
flag.Parse()
|
||||
|
||||
// Output build info (version, commit, date and builtBy)
|
||||
if versionFlag {
|
||||
var buildInfo string
|
||||
if date != "unknown" && builtBy != "unknown" {
|
||||
buildInfo = fmt.Sprintf("Built\t\t%s by %s\n", date, builtBy)
|
||||
}
|
||||
|
||||
if commit != "none" {
|
||||
buildInfo = fmt.Sprintf("Commit:\t\t%s\n%s", commit, buildInfo)
|
||||
}
|
||||
|
||||
if version == "dev" {
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if ok {
|
||||
version = bi.Main.Version
|
||||
if strings.HasPrefix(version, "v") {
|
||||
version = bi.Main.Version[1:]
|
||||
}
|
||||
if len(buildInfo) == 0 {
|
||||
fmt.Printf("version %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Version:\t%s\n%s", version, buildInfo)
|
||||
os.Exit(0)
|
||||
}
|
||||
cli.RunRevive()
|
||||
}
|
||||
|
Reference in New Issue
Block a user