github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/formatter/friendly.go (about)

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