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.

friendly.go 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package formatter
  2. import (
  3. "bytes"
  4. "fmt"
  5. "sort"
  6. "github.com/fatih/color"
  7. "github.com/mgechev/revive/lint"
  8. "github.com/olekukonko/tablewriter"
  9. )
  10. var newLines = map[rune]bool{
  11. 0x000A: true,
  12. 0x000B: true,
  13. 0x000C: true,
  14. 0x000D: true,
  15. 0x0085: true,
  16. 0x2028: true,
  17. 0x2029: true,
  18. }
  19. func getErrorEmoji() string {
  20. return color.RedString("✘")
  21. }
  22. func getWarningEmoji() string {
  23. return color.YellowString("⚠")
  24. }
  25. // Friendly is an implementation of the Formatter interface
  26. // which formats the errors to JSON.
  27. type Friendly struct {
  28. Metadata lint.FormatterMetadata
  29. }
  30. // Name returns the name of the formatter
  31. func (f *Friendly) Name() string {
  32. return "friendly"
  33. }
  34. // Format formats the failures gotten from the lint.
  35. func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
  36. errorMap := map[string]int{}
  37. warningMap := map[string]int{}
  38. totalErrors := 0
  39. totalWarnings := 0
  40. for failure := range failures {
  41. sev := severity(config, failure)
  42. f.printFriendlyFailure(failure, sev)
  43. if sev == lint.SeverityWarning {
  44. warningMap[failure.RuleName] = warningMap[failure.RuleName] + 1
  45. totalWarnings++
  46. }
  47. if sev == lint.SeverityError {
  48. errorMap[failure.RuleName] = errorMap[failure.RuleName] + 1
  49. totalErrors++
  50. }
  51. }
  52. f.printSummary(totalErrors, totalWarnings)
  53. f.printStatistics(color.RedString("Errors:"), errorMap)
  54. f.printStatistics(color.YellowString("Warnings:"), warningMap)
  55. return "", nil
  56. }
  57. func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Severity) {
  58. f.printHeaderRow(failure, severity)
  59. f.printFilePosition(failure)
  60. fmt.Println()
  61. fmt.Println()
  62. }
  63. func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity) {
  64. emoji := getWarningEmoji()
  65. if severity == lint.SeverityError {
  66. emoji = getErrorEmoji()
  67. }
  68. fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}}))
  69. }
  70. func (f *Friendly) printFilePosition(failure lint.Failure) {
  71. fmt.Printf(" %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column)
  72. }
  73. type statEntry struct {
  74. name string
  75. failures int
  76. }
  77. func (f *Friendly) printSummary(errors, warnings int) {
  78. emoji := getWarningEmoji()
  79. if errors > 0 {
  80. emoji = getErrorEmoji()
  81. }
  82. problemsLabel := "problems"
  83. if errors+warnings == 1 {
  84. problemsLabel = "problem"
  85. }
  86. warningsLabel := "warnings"
  87. if warnings == 1 {
  88. warningsLabel = "warning"
  89. }
  90. errorsLabel := "errors"
  91. if errors == 1 {
  92. errorsLabel = "error"
  93. }
  94. str := fmt.Sprintf("%d %s (%d %s, %d %s)", errors+warnings, problemsLabel, errors, errorsLabel, warnings, warningsLabel)
  95. if errors > 0 {
  96. fmt.Printf("%s %s\n", emoji, color.RedString(str))
  97. fmt.Println()
  98. return
  99. }
  100. if warnings > 0 {
  101. fmt.Printf("%s %s\n", emoji, color.YellowString(str))
  102. fmt.Println()
  103. return
  104. }
  105. }
  106. func (f *Friendly) printStatistics(header string, stats map[string]int) {
  107. if len(stats) == 0 {
  108. return
  109. }
  110. var data []statEntry
  111. for name, total := range stats {
  112. data = append(data, statEntry{name, total})
  113. }
  114. sort.Slice(data, func(i, j int) bool {
  115. return data[i].failures > data[j].failures
  116. })
  117. formatted := [][]string{}
  118. for _, entry := range data {
  119. formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name})
  120. }
  121. fmt.Println(header)
  122. fmt.Println(f.table(formatted))
  123. }
  124. func (f *Friendly) table(rows [][]string) string {
  125. buf := new(bytes.Buffer)
  126. table := tablewriter.NewWriter(buf)
  127. table.SetBorder(false)
  128. table.SetColumnSeparator("")
  129. table.SetRowSeparator("")
  130. table.SetAutoWrapText(false)
  131. table.AppendBulk(rows)
  132. table.Render()
  133. return buf.String()
  134. }