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:
@@ -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
165
formatter/formatter_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user