github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/cli/main.go (about) 1 package cli 2 3 import ( 4 "flag" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime/debug" 10 "strings" 11 12 "github.com/fatih/color" 13 "github.com/mgechev/dots" 14 "github.com/mitchellh/go-homedir" 15 "github.com/songshiyun/revive/config" 16 "github.com/songshiyun/revive/lint" 17 "github.com/songshiyun/revive/logging" 18 ) 19 20 var ( 21 version = "dev" 22 commit = "none" 23 date = "unknown" 24 builtBy = "unknown" 25 ) 26 27 func fail(err string) { 28 fmt.Fprintln(os.Stderr, err) 29 os.Exit(1) 30 } 31 32 // ExtraRule configures a new rule to be used with revive. 33 type ExtraRule struct { 34 Rule lint.Rule 35 DefaultConfig lint.RuleConfig 36 } 37 38 // NewExtraRule returns a configured extra rule 39 func NewExtraRule(rule lint.Rule, defaultConfig lint.RuleConfig) ExtraRule { 40 return ExtraRule{ 41 Rule: rule, 42 DefaultConfig: defaultConfig, 43 } 44 } 45 46 // RunRevive runs the CLI for revive. 47 func RunRevive(extraRules ...ExtraRule) { 48 log, err := logging.GetLogger() 49 if err != nil { 50 fail(err.Error()) 51 } 52 53 formatter, err := config.GetFormatter(formatterName) 54 if err != nil { 55 fail(err.Error()) 56 } 57 58 conf, err := mergeConf() 59 if err != nil { 60 fail(err.Error()) 61 } 62 63 if setExitStatus { 64 conf.ErrorCode = 1 65 conf.WarningCode = 1 66 } 67 68 extraRuleInstances := make([]lint.Rule, len(extraRules)) 69 for i, extraRule := range extraRules { 70 extraRuleInstances[i] = extraRule.Rule 71 72 ruleName := extraRule.Rule.Name() 73 _, isRuleAlreadyConfigured := conf.Rules[ruleName] 74 if !isRuleAlreadyConfigured { 75 conf.Rules[ruleName] = extraRule.DefaultConfig 76 } 77 } 78 79 lintingRules, err := config.GetLintingRules(conf, extraRuleInstances) 80 if err != nil { 81 fail(err.Error()) 82 } 83 84 log.Println("Config loaded") 85 86 if len(excludePaths) == 0 { // if no excludes were set in the command line 87 excludePaths = conf.Exclude // use those from the configuration 88 } 89 90 packages, err := getPackages(excludePaths) 91 if err != nil { 92 fail(err.Error()) 93 } 94 revive := lint.New(func(file string) ([]byte, error) { 95 return ioutil.ReadFile(file) 96 }, maxOpenFiles) 97 98 failures, err := revive.Lint(packages, lintingRules, *conf) 99 if err != nil { 100 fail(err.Error()) 101 } 102 103 formatChan := make(chan lint.Failure) 104 exitChan := make(chan bool) 105 106 var output string 107 go (func() { 108 output, err = formatter.Format(formatChan, *conf) 109 if err != nil { 110 fail(err.Error()) 111 } 112 exitChan <- true 113 })() 114 115 exitCode := 0 116 for f := range failures { 117 if f.Confidence < conf.Confidence { 118 continue 119 } 120 if exitCode == 0 { 121 exitCode = conf.WarningCode 122 } 123 if c, ok := conf.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError { 124 exitCode = conf.ErrorCode 125 } 126 if c, ok := conf.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError { 127 exitCode = conf.ErrorCode 128 } 129 130 formatChan <- f 131 } 132 133 close(formatChan) 134 <-exitChan 135 if output != "" { 136 fmt.Println(output) 137 } 138 139 os.Exit(exitCode) 140 } 141 142 func normalizeSplit(strs []string) []string { 143 res := []string{} 144 for _, s := range strs { 145 t := strings.Trim(s, " \t") 146 if len(t) > 0 { 147 res = append(res, t) 148 } 149 } 150 return res 151 } 152 153 func getPackages(excludePaths arrayFlags) ([][]string, error) { 154 globs := normalizeSplit(flag.Args()) 155 if len(globs) == 0 { 156 //globs = append(globs, ".") 157 } 158 changeFiles, err := GetChangedFiles(rootPath) 159 if err != nil { 160 return nil, err 161 } 162 globs = append(globs, changeFiles...) 163 packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths)) 164 if err != nil { 165 return nil, err 166 } 167 168 return packages, nil 169 } 170 171 type arrayFlags []string 172 173 func (i *arrayFlags) String() string { 174 return strings.Join([]string(*i), " ") 175 } 176 177 func (i *arrayFlags) Set(value string) error { 178 *i = append(*i, value) 179 return nil 180 } 181 182 var ( 183 configPath string 184 excludePaths arrayFlags 185 formatterName string 186 help bool 187 versionFlag bool 188 setExitStatus bool 189 maxOpenFiles int 190 rootPath string 191 ) 192 193 var originalUsage = flag.Usage 194 195 func getLogo() string { 196 return color.YellowString(` _ __ _____ _(_)__ _____ 197 | '__/ _ \ \ / / \ \ / / _ \ 198 | | | __/\ V /| |\ V / __/ 199 |_| \___| \_/ |_| \_/ \___|`) 200 } 201 202 func getCall() string { 203 return color.MagentaString("revive -config c.toml -formatter friendly -exclude a.go -exclude b.go ./...") 204 } 205 206 func getBanner() string { 207 return fmt.Sprintf(` 208 %s 209 210 Example: 211 %s 212 `, getLogo(), getCall()) 213 } 214 215 func buildDefaultConfigPath() string { 216 var result string 217 if homeDir, err := homedir.Dir(); err == nil { 218 result = filepath.Join(homeDir, "revive.toml") 219 if _, err := os.Stat(result); err != nil { 220 result = "" 221 } 222 } 223 224 return result 225 } 226 227 func init() { 228 // Force colorizing for no TTY environments 229 if os.Getenv("REVIVE_FORCE_COLOR") == "1" { 230 color.NoColor = false 231 } 232 233 flag.Usage = func() { 234 fmt.Println(getBanner()) 235 originalUsage() 236 } 237 238 // command line help strings 239 const ( 240 configUsage = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)" 241 excludeUsage = "list of globs which specify files to be excluded (i.e. -exclude foo/...)" 242 formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)" 243 versionUsage = "get revive version" 244 exitStatusUsage = "set exit status to 1 if any issues are found, overwrites errorCode and warningCode in config" 245 maxOpenFilesUsage = "maximum number of open files at the same time" 246 rootPathUsage = "the root path for the git directory" 247 ) 248 249 defaultConfigPath := buildDefaultConfigPath() 250 251 flag.StringVar(&configPath, "config", defaultConfigPath, configUsage) 252 flag.Var(&excludePaths, "exclude", excludeUsage) 253 flag.StringVar(&formatterName, "formatter", "", formatterUsage) 254 flag.BoolVar(&versionFlag, "version", false, versionUsage) 255 flag.BoolVar(&setExitStatus, "set_exit_status", false, exitStatusUsage) 256 flag.IntVar(&maxOpenFiles, "max_open_files", 0, maxOpenFilesUsage) 257 flag.StringVar(&rootPath, "root", "./", rootPathUsage) 258 flag.Parse() 259 260 // Output build info (version, commit, date and builtBy) 261 if versionFlag { 262 var buildInfo string 263 if date != "unknown" && builtBy != "unknown" { 264 buildInfo = fmt.Sprintf("Built\t\t%s by %s\n", date, builtBy) 265 } 266 267 if commit != "none" { 268 buildInfo = fmt.Sprintf("Commit:\t\t%s\n%s", commit, buildInfo) 269 } 270 271 if version == "dev" { 272 bi, ok := debug.ReadBuildInfo() 273 if ok { 274 version = bi.Main.Version 275 if strings.HasPrefix(version, "v") { 276 version = bi.Main.Version[1:] 277 } 278 if len(buildInfo) == 0 { 279 fmt.Printf("version %s\n", version) 280 os.Exit(0) 281 } 282 } 283 } 284 285 fmt.Printf("Version:\t%s\n%s", version, buildInfo) 286 os.Exit(0) 287 } 288 }