1
0
mirror of https://github.com/mgechev/revive.git synced 2024-11-21 17:16:40 +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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 207 additions and 32 deletions

View File

@ -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
}

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 (
"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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}