Skip to content

Commit

Permalink
formatters return output
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
WillAbides committed Oct 25, 2023
1 parent 1ef3c0f commit 4cc2992
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 32 deletions.
6 changes: 4 additions & 2 deletions formatter/default.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package formatter

import (
"bytes"
"fmt"

"github.com/mgechev/revive/lint"
Expand All @@ -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 changes: 165 additions & 0 deletions formatter/formatter_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
46 changes: 24 additions & 22 deletions formatter/friendly.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package formatter
import (
"bytes"
"fmt"
"io"
"sort"

"github.com/fatih/color"
Expand Down Expand Up @@ -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++
Expand All @@ -47,37 +49,37 @@ 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 {
name string
failures int
}

func (*Friendly) printSummary(errors, warnings int) {
func (*Friendly) printSummary(w io.Writer, errors, warnings int) {
emoji := getWarningEmoji()
if errors > 0 {
emoji = getErrorEmoji()
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions formatter/ndjson.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package formatter

import (
"bytes"
"encoding/json"
"os"

"github.com/mgechev/revive/lint"
)
Expand All @@ -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)
Expand All @@ -30,5 +31,5 @@ func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string,
return "", err
}
}
return "", nil
return buf.String(), nil
}
6 changes: 4 additions & 2 deletions formatter/plain.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package formatter

import (
"bytes"
"fmt"

"github.com/mgechev/revive/lint"
Expand All @@ -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
}
9 changes: 6 additions & 3 deletions formatter/unix.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package formatter

import (
"bytes"
"fmt"

"github.com/mgechev/revive/lint"
)

// 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
}
Expand All @@ -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
}

0 comments on commit 4cc2992

Please sign in to comment.