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.5KB

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