You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
7.0 KiB

  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Copyright (c) 2018 Minko Gechev. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. // +build ignore
  6. package main
  7. import (
  8. "flag"
  9. "fmt"
  10. "io/ioutil"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "github.com/BurntSushi/toml"
  15. "github.com/mgechev/dots"
  16. "github.com/mgechev/revive/formatter"
  17. "github.com/mgechev/revive/lint"
  18. "github.com/mgechev/revive/rule"
  19. "github.com/mitchellh/go-homedir"
  20. )
  21. func fail(err string) {
  22. fmt.Fprintln(os.Stderr, err)
  23. os.Exit(1)
  24. }
  25. var defaultRules = []lint.Rule{
  26. &rule.VarDeclarationsRule{},
  27. &rule.PackageCommentsRule{},
  28. &rule.DotImportsRule{},
  29. &rule.BlankImportsRule{},
  30. &rule.ExportedRule{},
  31. &rule.VarNamingRule{},
  32. &rule.IndentErrorFlowRule{},
  33. &rule.IfReturnRule{},
  34. &rule.RangeRule{},
  35. &rule.ErrorfRule{},
  36. &rule.ErrorNamingRule{},
  37. &rule.ErrorStringsRule{},
  38. &rule.ReceiverNamingRule{},
  39. &rule.IncrementDecrementRule{},
  40. &rule.ErrorReturnRule{},
  41. &rule.UnexportedReturnRule{},
  42. &rule.TimeNamingRule{},
  43. &rule.ContextKeysType{},
  44. &rule.ContextAsArgumentRule{},
  45. }
  46. var allRules = append([]lint.Rule{
  47. &rule.ArgumentsLimitRule{},
  48. &rule.CyclomaticRule{},
  49. &rule.FileHeaderRule{},
  50. &rule.EmptyBlockRule{},
  51. &rule.SuperfluousElseRule{},
  52. &rule.ConfusingNamingRule{},
  53. &rule.GetReturnRule{},
  54. &rule.ModifiesParamRule{},
  55. &rule.ConfusingResultsRule{},
  56. &rule.DeepExitRule{},
  57. &rule.UnusedParamRule{},
  58. &rule.UnreachableCodeRule{},
  59. &rule.AddConstantRule{},
  60. &rule.FlagParamRule{},
  61. &rule.UnnecessaryStmtRule{},
  62. &rule.StructTagRule{},
  63. &rule.ModifiesValRecRule{},
  64. &rule.ConstantLogicalExprRule{},
  65. &rule.BoolLiteralRule{},
  66. &rule.RedefinesBuiltinIDRule{},
  67. &rule.ImportsBlacklistRule{},
  68. &rule.FunctionResultsLimitRule{},
  69. &rule.MaxPublicStructsRule{},
  70. &rule.RangeValInClosureRule{},
  71. &rule.RangeValAddress{},
  72. &rule.WaitGroupByValueRule{},
  73. &rule.AtomicRule{},
  74. &rule.EmptyLinesRule{},
  75. &rule.LineLengthLimitRule{},
  76. &rule.CallToGCRule{},
  77. &rule.DuplicatedImportsRule{},
  78. &rule.ImportShadowingRule{},
  79. &rule.BareReturnRule{},
  80. &rule.UnusedReceiverRule{},
  81. &rule.UnhandledErrorRule{},
  82. &rule.CognitiveComplexityRule{},
  83. &rule.StringOfIntRule{},
  84. }, defaultRules...)
  85. var allFormatters = []lint.Formatter{
  86. &formatter.Stylish{},
  87. &formatter.Friendly{},
  88. &formatter.JSON{},
  89. &formatter.NDJSON{},
  90. &formatter.Default{},
  91. &formatter.Unix{},
  92. &formatter.Checkstyle{},
  93. &formatter.Plain{},
  94. }
  95. func getFormatters() map[string]lint.Formatter {
  96. result := map[string]lint.Formatter{}
  97. for _, f := range allFormatters {
  98. result[f.Name()] = f
  99. }
  100. return result
  101. }
  102. func getLintingRules(config *lint.Config) []lint.Rule {
  103. rulesMap := map[string]lint.Rule{}
  104. for _, r := range allRules {
  105. rulesMap[r.Name()] = r
  106. }
  107. lintingRules := []lint.Rule{}
  108. for name := range config.Rules {
  109. rule, ok := rulesMap[name]
  110. if !ok {
  111. fail("cannot find rule: " + name)
  112. }
  113. lintingRules = append(lintingRules, rule)
  114. }
  115. return lintingRules
  116. }
  117. func parseConfig(path string) *lint.Config {
  118. config := &lint.Config{}
  119. file, err := ioutil.ReadFile(path)
  120. if err != nil {
  121. fail("cannot read the config file")
  122. }
  123. _, err = toml.Decode(string(file), config)
  124. if err != nil {
  125. fail("cannot parse the config file: " + err.Error())
  126. }
  127. return config
  128. }
  129. func normalizeConfig(config *lint.Config) {
  130. if config.Confidence == 0 {
  131. config.Confidence = 0.8
  132. }
  133. severity := config.Severity
  134. if severity != "" {
  135. for k, v := range config.Rules {
  136. if v.Severity == "" {
  137. v.Severity = severity
  138. }
  139. config.Rules[k] = v
  140. }
  141. for k, v := range config.Directives {
  142. if v.Severity == "" {
  143. v.Severity = severity
  144. }
  145. config.Directives[k] = v
  146. }
  147. }
  148. }
  149. func getConfig() *lint.Config {
  150. config := defaultConfig()
  151. if configPath != "" {
  152. config = parseConfig(configPath)
  153. }
  154. normalizeConfig(config)
  155. return config
  156. }
  157. func getFormatter() lint.Formatter {
  158. formatters := getFormatters()
  159. formatter := formatters["default"]
  160. if formatterName != "" {
  161. f, ok := formatters[formatterName]
  162. if !ok {
  163. fail("unknown formatter " + formatterName)
  164. }
  165. formatter = f
  166. }
  167. return formatter
  168. }
  169. func buildDefaultConfigPath() string {
  170. var result string
  171. if homeDir, err := homedir.Dir(); err == nil {
  172. result = filepath.Join(homeDir, "revive.toml")
  173. if _, err := os.Stat(result); err != nil {
  174. result = ""
  175. }
  176. }
  177. return result
  178. }
  179. func defaultConfig() *lint.Config {
  180. defaultConfig := lint.Config{
  181. Confidence: 0.0,
  182. Severity: lint.SeverityWarning,
  183. Rules: map[string]lint.RuleConfig{},
  184. }
  185. for _, r := range defaultRules {
  186. defaultConfig.Rules[r.Name()] = lint.RuleConfig{}
  187. }
  188. return &defaultConfig
  189. }
  190. func normalizeSplit(strs []string) []string {
  191. res := []string{}
  192. for _, s := range strs {
  193. t := strings.Trim(s, " \t")
  194. if len(t) > 0 {
  195. res = append(res, t)
  196. }
  197. }
  198. return res
  199. }
  200. func getPackages() [][]string {
  201. globs := normalizeSplit(flag.Args())
  202. if len(globs) == 0 {
  203. globs = append(globs, ".")
  204. }
  205. packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
  206. if err != nil {
  207. fail(err.Error())
  208. }
  209. return packages
  210. }
  211. type arrayFlags []string
  212. func (i *arrayFlags) String() string {
  213. return strings.Join([]string(*i), " ")
  214. }
  215. func (i *arrayFlags) Set(value string) error {
  216. *i = append(*i, value)
  217. return nil
  218. }
  219. var configPath string
  220. var excludePaths arrayFlags
  221. var formatterName string
  222. var help bool
  223. var originalUsage = flag.Usage
  224. func init() {
  225. flag.Usage = func() {
  226. originalUsage()
  227. }
  228. // command line help strings
  229. const (
  230. configUsage = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
  231. excludeUsage = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
  232. formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
  233. )
  234. defaultConfigPath := buildDefaultConfigPath()
  235. flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
  236. flag.Var(&excludePaths, "exclude", excludeUsage)
  237. flag.StringVar(&formatterName, "formatter", "", formatterUsage)
  238. flag.Parse()
  239. }
  240. func main() {
  241. config := getConfig()
  242. formatter := getFormatter()
  243. packages := getPackages()
  244. revive := lint.New(func(file string) ([]byte, error) {
  245. return ioutil.ReadFile(file)
  246. })
  247. lintingRules := getLintingRules(config)
  248. failures, err := revive.Lint(packages, lintingRules, *config)
  249. if err != nil {
  250. fail(err.Error())
  251. }
  252. formatChan := make(chan lint.Failure)
  253. exitChan := make(chan bool)
  254. var output string
  255. go (func() {
  256. output, err = formatter.Format(formatChan, *config)
  257. if err != nil {
  258. fail(err.Error())
  259. }
  260. exitChan <- true
  261. })()
  262. exitCode := 0
  263. for f := range failures {
  264. if f.Confidence < config.Confidence {
  265. continue
  266. }
  267. if exitCode == 0 {
  268. exitCode = config.WarningCode
  269. }
  270. if c, ok := config.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
  271. exitCode = config.ErrorCode
  272. }
  273. if c, ok := config.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
  274. exitCode = config.ErrorCode
  275. }
  276. formatChan <- f
  277. }
  278. close(formatChan)
  279. <-exitChan
  280. if output != "" {
  281. fmt.Println(output)
  282. }
  283. os.Exit(exitCode)
  284. }