diff --git a/formatter/default.go b/formatter/default.go index f76a7b29a..2d5a04434 100644 --- a/formatter/default.go +++ b/formatter/default.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -19,8 +20,9 @@ func (*Default) Name() string { // Format formats the failures gotten from the lint. func (*Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure) + fmt.Fprintf(&buf, "%v: %s\n", failure.Position.Start, failure.Failure) } - return "", nil + return buf.String(), nil } diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go new file mode 100644 index 000000000..e0d4e9dbf --- /dev/null +++ b/formatter/formatter_test.go @@ -0,0 +1,165 @@ +package formatter_test + +import ( + "go/token" + "os" + "strings" + "testing" + + "github.com/mgechev/revive/formatter" + "github.com/mgechev/revive/lint" +) + +func TestFormatter(t *testing.T) { + lintFailure := lint.Failure{ + Failure: "test failure", + RuleName: "rule", + Category: "cat", + Position: lint.FailurePosition{ + Start: token.Position{ + Filename: "test.go", + Line: 2, + Column: 5, + }, + End: token.Position{ + Filename: "test.go", + Line: 2, + Column: 10, + }, + }, + } + for _, td := range []struct { + formatter lint.Formatter + want string + }{ + { + formatter: &formatter.Checkstyle{}, + want: ` + + + + + + +`, + }, + { + formatter: &formatter.Default{}, + want: `test.go:2:5: test failure`, + }, + { + formatter: &formatter.Friendly{}, + want: ` +⚠ https://revive.run/r#rule test failure + test.go:2:5 + +⚠ 1 problem (0 errors, 1 warning) + +Warnings: + 1 rule +`, + }, + { + formatter: &formatter.JSON{}, + want: `[{"Severity":"warning","Failure":"test failure","RuleName":"rule","Category":"cat","Position":{"Start":{"Filename":"test.go","Offset":0,"Line":2,"Column":5},"End":{"Filename":"test.go","Offset":0,"Line":2,"Column":10}},"Confidence":0,"ReplacementLine":""}]`, + }, + { + formatter: &formatter.NDJSON{}, + want: `{"Severity":"warning","Failure":"test failure","RuleName":"rule","Category":"cat","Position":{"Start":{"Filename":"test.go","Offset":0,"Line":2,"Column":5},"End":{"Filename":"test.go","Offset":0,"Line":2,"Column":10}},"Confidence":0,"ReplacementLine":""}`, + }, + { + formatter: &formatter.Plain{}, + want: `test.go:2:5: test failure https://revive.run/r#rule`, + }, + { + formatter: &formatter.Sarif{}, + want: ` +{ + "runs": [ + { + "results": [ + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test.go" + }, + "region": { + "startColumn": 5, + "startLine": 2 + } + } + } + ], + "message": { + "text": "test failure" + }, + "ruleId": "rule" + } + ], + "tool": { + "driver": { + "informationUri": "https://revive.run", + "name": "revive" + } + } + } + ], + "version": "2.1.0" +} +`, + }, + { + formatter: &formatter.Stylish{}, + want: ` +test.go + (2, 5) https://revive.run/r#rule test failure + + + ✖ 1 problem (0 errors) (1 warnings) +`, + }, + { + formatter: &formatter.Unix{}, + want: `test.go:2:5: [rule] test failure`, + }, + } { + t.Run(td.formatter.Name(), func(t *testing.T) { + dir := t.TempDir() + realStdout := os.Stdout + fakeStdout, err := os.Create(dir + "/fakeStdout") + if err != nil { + t.Fatal(err) + } + os.Stdout = fakeStdout + defer func() { + os.Stdout = realStdout + }() + failures := make(chan lint.Failure, 10) + failures <- lintFailure + close(failures) + output, err := td.formatter.Format(failures, lint.Config{}) + if err != nil { + t.Fatal(err) + } + os.Stdout = realStdout + err = fakeStdout.Close() + if err != nil { + t.Fatal(err) + } + stdout, err := os.ReadFile(fakeStdout.Name()) + if err != nil { + t.Fatal(err) + } + if len(stdout) > 0 { + t.Errorf("formatter wrote to stdout: %q", stdout) + } + got := strings.TrimSpace(output) + want := strings.TrimSpace(td.want) + if got != want { + t.Errorf("got %q, want %q", got, want) + } + }) + } +} diff --git a/formatter/friendly.go b/formatter/friendly.go index ced8fa46c..5ff329a23 100644 --- a/formatter/friendly.go +++ b/formatter/friendly.go @@ -3,6 +3,7 @@ package formatter import ( "bytes" "fmt" + "io" "sort" "github.com/fatih/color" @@ -31,13 +32,14 @@ func (*Friendly) Name() string { // Format formats the failures gotten from the lint. func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + var buf bytes.Buffer errorMap := map[string]int{} warningMap := map[string]int{} totalErrors := 0 totalWarnings := 0 for failure := range failures { sev := severity(config, failure) - f.printFriendlyFailure(failure, sev) + f.printFriendlyFailure(&buf, failure, sev) if sev == lint.SeverityWarning { warningMap[failure.RuleName]++ totalWarnings++ @@ -47,29 +49,29 @@ func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (str totalErrors++ } } - f.printSummary(totalErrors, totalWarnings) - f.printStatistics(color.RedString("Errors:"), errorMap) - f.printStatistics(color.YellowString("Warnings:"), warningMap) - return "", nil + f.printSummary(&buf, totalErrors, totalWarnings) + f.printStatistics(&buf, color.RedString("Errors:"), errorMap) + f.printStatistics(&buf, color.YellowString("Warnings:"), warningMap) + return buf.String(), nil } -func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Severity) { - f.printHeaderRow(failure, severity) - f.printFilePosition(failure) - fmt.Println() - fmt.Println() +func (f *Friendly) printFriendlyFailure(w io.Writer, failure lint.Failure, severity lint.Severity) { + f.printHeaderRow(w, failure, severity) + f.printFilePosition(w, failure) + fmt.Fprintln(w) + fmt.Fprintln(w) } -func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity) { +func (f *Friendly) printHeaderRow(w io.Writer, failure lint.Failure, severity lint.Severity) { emoji := getWarningEmoji() if severity == lint.SeverityError { emoji = getErrorEmoji() } - fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) + fmt.Fprint(w, f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) } -func (*Friendly) printFilePosition(failure lint.Failure) { - fmt.Printf(" %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) +func (*Friendly) printFilePosition(w io.Writer, failure lint.Failure) { + fmt.Fprintf(w, " %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) } type statEntry struct { @@ -77,7 +79,7 @@ type statEntry struct { failures int } -func (*Friendly) printSummary(errors, warnings int) { +func (*Friendly) printSummary(w io.Writer, errors, warnings int) { emoji := getWarningEmoji() if errors > 0 { emoji = getErrorEmoji() @@ -96,18 +98,18 @@ func (*Friendly) printSummary(errors, warnings int) { } str := fmt.Sprintf("%d %s (%d %s, %d %s)", errors+warnings, problemsLabel, errors, errorsLabel, warnings, warningsLabel) if errors > 0 { - fmt.Printf("%s %s\n", emoji, color.RedString(str)) - fmt.Println() + fmt.Fprintf(w, "%s %s\n", emoji, color.RedString(str)) + fmt.Fprintln(w) return } if warnings > 0 { - fmt.Printf("%s %s\n", emoji, color.YellowString(str)) - fmt.Println() + fmt.Fprintf(w, "%s %s\n", emoji, color.YellowString(str)) + fmt.Fprintln(w) return } } -func (f *Friendly) printStatistics(header string, stats map[string]int) { +func (f *Friendly) printStatistics(w io.Writer, header string, stats map[string]int) { if len(stats) == 0 { return } @@ -122,8 +124,8 @@ func (f *Friendly) printStatistics(header string, stats map[string]int) { for _, entry := range data { formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name}) } - fmt.Println(header) - fmt.Println(f.table(formatted)) + fmt.Fprintln(w, header) + fmt.Fprintln(w, f.table(formatted)) } func (*Friendly) table(rows [][]string) string { diff --git a/formatter/ndjson.go b/formatter/ndjson.go index a02d9c80f..58b35dc44 100644 --- a/formatter/ndjson.go +++ b/formatter/ndjson.go @@ -1,8 +1,8 @@ package formatter import ( + "bytes" "encoding/json" - "os" "github.com/mgechev/revive/lint" ) @@ -20,7 +20,8 @@ func (*NDJSON) Name() string { // Format formats the failures gotten from the lint. func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { - enc := json.NewEncoder(os.Stdout) + var buf bytes.Buffer + enc := json.NewEncoder(&buf) for failure := range failures { obj := jsonObject{} obj.Severity = severity(config, failure) @@ -30,5 +31,5 @@ func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, return "", err } } - return "", nil + return buf.String(), nil } diff --git a/formatter/plain.go b/formatter/plain.go index 6e083bcfd..09ebf6cdc 100644 --- a/formatter/plain.go +++ b/formatter/plain.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -19,8 +20,9 @@ func (*Plain) Name() string { // Format formats the failures gotten from the lint. func (*Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) + fmt.Fprintf(&buf, "%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) } - return "", nil + return buf.String(), nil } diff --git a/formatter/unix.go b/formatter/unix.go index ef2f1613a..e46f3c275 100644 --- a/formatter/unix.go +++ b/formatter/unix.go @@ -1,6 +1,7 @@ package formatter import ( + "bytes" "fmt" "github.com/mgechev/revive/lint" @@ -8,7 +9,8 @@ import ( // Unix is an implementation of the Formatter interface // which formats the errors to a simple line based error format -// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) +// +// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) type Unix struct { Metadata lint.FormatterMetadata } @@ -20,8 +22,9 @@ func (*Unix) Name() string { // Format formats the failures gotten from the lint. func (*Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + var buf bytes.Buffer for failure := range failures { - fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) + fmt.Fprintf(&buf, "%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) } - return "", nil + return buf.String(), nil }