1
0
mirror of https://github.com/mgechev/revive.git synced 2025-11-23 22:04:49 +02:00

formatters return output (#921)

Some of the formatters were writing directly to stdout instead of returning the output. That made them more difficult to use them with revivelib. This PR updates those formatters to write to a buffer and return the resulting string.
This commit is contained in:
WillAbides
2023-10-29 02:05:08 -05:00
committed by GitHub
parent 25ae73a67a
commit cb72bd880d
6 changed files with 207 additions and 32 deletions

View File

@@ -1,6 +1,7 @@
package formatter package formatter
import ( import (
"bytes"
"fmt" "fmt"
"github.com/mgechev/revive/lint" "github.com/mgechev/revive/lint"
@@ -19,8 +20,9 @@ func (*Default) Name() string {
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (*Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { func (*Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
var buf bytes.Buffer
for failure := range failures { 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
} }

165
formatter/formatter_test.go Normal file
View File

@@ -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: `
<?xml version='1.0' encoding='UTF-8'?>
<checkstyle version="5.0">
<file name="test.go">
<error line="2" column="5" message="test failure (confidence 0)" severity="warning" source="revive/rule"/>
</file>
</checkstyle>
`,
},
{
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)
}
})
}
}

View File

@@ -3,6 +3,7 @@ package formatter
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"sort" "sort"
"github.com/fatih/color" "github.com/fatih/color"
@@ -31,13 +32,14 @@ func (*Friendly) Name() string {
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var buf bytes.Buffer
errorMap := map[string]int{} errorMap := map[string]int{}
warningMap := map[string]int{} warningMap := map[string]int{}
totalErrors := 0 totalErrors := 0
totalWarnings := 0 totalWarnings := 0
for failure := range failures { for failure := range failures {
sev := severity(config, failure) sev := severity(config, failure)
f.printFriendlyFailure(failure, sev) f.printFriendlyFailure(&buf, failure, sev)
if sev == lint.SeverityWarning { if sev == lint.SeverityWarning {
warningMap[failure.RuleName]++ warningMap[failure.RuleName]++
totalWarnings++ totalWarnings++
@@ -47,29 +49,29 @@ func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (str
totalErrors++ totalErrors++
} }
} }
f.printSummary(totalErrors, totalWarnings) f.printSummary(&buf, totalErrors, totalWarnings)
f.printStatistics(color.RedString("Errors:"), errorMap) f.printStatistics(&buf, color.RedString("Errors:"), errorMap)
f.printStatistics(color.YellowString("Warnings:"), warningMap) f.printStatistics(&buf, color.YellowString("Warnings:"), warningMap)
return "", nil return buf.String(), nil
} }
func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Severity) { func (f *Friendly) printFriendlyFailure(w io.Writer, failure lint.Failure, severity lint.Severity) {
f.printHeaderRow(failure, severity) f.printHeaderRow(w, failure, severity)
f.printFilePosition(failure) f.printFilePosition(w, failure)
fmt.Println() fmt.Fprintln(w)
fmt.Println() 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() emoji := getWarningEmoji()
if severity == lint.SeverityError { if severity == lint.SeverityError {
emoji = getErrorEmoji() 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) { func (*Friendly) printFilePosition(w io.Writer, failure lint.Failure) {
fmt.Printf(" %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) fmt.Fprintf(w, " %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column)
} }
type statEntry struct { type statEntry struct {
@@ -77,7 +79,7 @@ type statEntry struct {
failures int failures int
} }
func (*Friendly) printSummary(errors, warnings int) { func (*Friendly) printSummary(w io.Writer, errors, warnings int) {
emoji := getWarningEmoji() emoji := getWarningEmoji()
if errors > 0 { if errors > 0 {
emoji = getErrorEmoji() 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) str := fmt.Sprintf("%d %s (%d %s, %d %s)", errors+warnings, problemsLabel, errors, errorsLabel, warnings, warningsLabel)
if errors > 0 { if errors > 0 {
fmt.Printf("%s %s\n", emoji, color.RedString(str)) fmt.Fprintf(w, "%s %s\n", emoji, color.RedString(str))
fmt.Println() fmt.Fprintln(w)
return return
} }
if warnings > 0 { if warnings > 0 {
fmt.Printf("%s %s\n", emoji, color.YellowString(str)) fmt.Fprintf(w, "%s %s\n", emoji, color.YellowString(str))
fmt.Println() fmt.Fprintln(w)
return 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 { if len(stats) == 0 {
return return
} }
@@ -122,8 +124,8 @@ func (f *Friendly) printStatistics(header string, stats map[string]int) {
for _, entry := range data { for _, entry := range data {
formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name}) formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name})
} }
fmt.Println(header) fmt.Fprintln(w, header)
fmt.Println(f.table(formatted)) fmt.Fprintln(w, f.table(formatted))
} }
func (*Friendly) table(rows [][]string) string { func (*Friendly) table(rows [][]string) string {

View File

@@ -1,8 +1,8 @@
package formatter package formatter
import ( import (
"bytes"
"encoding/json" "encoding/json"
"os"
"github.com/mgechev/revive/lint" "github.com/mgechev/revive/lint"
) )
@@ -20,7 +20,8 @@ func (*NDJSON) Name() string {
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { 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 { for failure := range failures {
obj := jsonObject{} obj := jsonObject{}
obj.Severity = severity(config, failure) obj.Severity = severity(config, failure)
@@ -30,5 +31,5 @@ func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string,
return "", err return "", err
} }
} }
return "", nil return buf.String(), nil
} }

View File

@@ -1,6 +1,7 @@
package formatter package formatter
import ( import (
"bytes"
"fmt" "fmt"
"github.com/mgechev/revive/lint" "github.com/mgechev/revive/lint"
@@ -19,8 +20,9 @@ func (*Plain) Name() string {
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (*Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { func (*Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
var buf bytes.Buffer
for failure := range failures { 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
} }

View File

@@ -1,6 +1,7 @@
package formatter package formatter
import ( import (
"bytes"
"fmt" "fmt"
"github.com/mgechev/revive/lint" "github.com/mgechev/revive/lint"
@@ -8,7 +9,8 @@ import (
// Unix is an implementation of the Formatter interface // Unix is an implementation of the Formatter interface
// which formats the errors to a simple line based error format // 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 { type Unix struct {
Metadata lint.FormatterMetadata Metadata lint.FormatterMetadata
} }
@@ -20,8 +22,9 @@ func (*Unix) Name() string {
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (*Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { func (*Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
var buf bytes.Buffer
for failure := range failures { 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
} }