Skip to content

Commit

Permalink
Add support for multiple outputs (golangci#2386)
Browse files Browse the repository at this point in the history
  • Loading branch information
lafriks authored and SeigeC committed Apr 4, 2023
1 parent a0344a9 commit 36ad3b2
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 56 deletions.
4 changes: 4 additions & 0 deletions .golangci.example.yml
Expand Up @@ -62,6 +62,10 @@ run:
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
# default is "colored-line-number"
# multiple can be specified by separating them by comma, output can be provided
# for each of them by separating format name and path by colon symbol.
# Output path can be either `stdout`, `stderr` or path to the file to write to.
# Example "checkstyle:report.json,colored-line-number"
format: colored-line-number

# print lines of code with issue, default is true
Expand Down
75 changes: 61 additions & 14 deletions pkg/commands/run.go
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/golangci/golangci-lint/pkg/result/processors"
)

const defaultFileMode = 0644

func getDefaultIssueExcludeHelp() string {
parts := []string{"Use or not use default excludes:"}
for _, ep := range config.DefaultExcludePatterns {
Expand Down Expand Up @@ -400,44 +402,89 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
return err // XXX: don't loose type
}

p, err := e.createPrinter()
if err != nil {
return err
formats := strings.Split(e.cfg.Output.Format, ",")
for _, format := range formats {
out := strings.SplitN(format, ":", 2)
if len(out) < 2 {
out = append(out, "")
}

err := e.printReports(ctx, issues, out[1], out[0])
if err != nil {
return err
}
}

e.setExitCodeIfIssuesFound(issues)

e.fileCache.PrintStats(e.log)

return nil
}

func (e *Executor) printReports(ctx context.Context, issues []result.Issue, path, format string) error {
w, shouldClose, err := e.createWriter(path)
if err != nil {
return fmt.Errorf("can't create output for %s: %w", path, err)
}

p, err := e.createPrinter(format, w)
if err != nil {
if file, ok := w.(io.Closer); shouldClose && ok {
_ = file.Close()
}
return err
}

if err = p.Print(ctx, issues); err != nil {
if file, ok := w.(io.Closer); shouldClose && ok {
_ = file.Close()
}
return fmt.Errorf("can't print %d issues: %s", len(issues), err)
}

e.fileCache.PrintStats(e.log)
if file, ok := w.(io.Closer); shouldClose && ok {
_ = file.Close()
}

return nil
}

func (e *Executor) createPrinter() (printers.Printer, error) {
func (e *Executor) createWriter(path string) (io.Writer, bool, error) {
if path == "" || path == "stdout" {
return logutils.StdOut, false, nil
}
if path == "stderr" {
return logutils.StdErr, false, nil
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode)
if err != nil {
return nil, false, err
}
return f, true, nil
}

func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, error) {
var p printers.Printer
format := e.cfg.Output.Format
switch format {
case config.OutFormatJSON:
p = printers.NewJSON(&e.reportData)
p = printers.NewJSON(&e.reportData, w)
case config.OutFormatColoredLineNumber, config.OutFormatLineNumber:
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
e.log.Child("text_printer"))
e.log.Child("text_printer"), w)
case config.OutFormatTab:
p = printers.NewTab(e.cfg.Output.PrintLinterName, e.log.Child("tab_printer"))
p = printers.NewTab(e.cfg.Output.PrintLinterName, e.log.Child("tab_printer"), w)
case config.OutFormatCheckstyle:
p = printers.NewCheckstyle()
p = printers.NewCheckstyle(w)
case config.OutFormatCodeClimate:
p = printers.NewCodeClimate()
p = printers.NewCodeClimate(w)
case config.OutFormatHTML:
p = printers.NewHTML()
p = printers.NewHTML(w)
case config.OutFormatJunitXML:
p = printers.NewJunitXML()
p = printers.NewJunitXML(w)
case config.OutFormatGithubActions:
p = printers.NewGithub()
p = printers.NewGithub(w)
default:
return nil, fmt.Errorf("unknown output format %s", format)
}
Expand Down
18 changes: 12 additions & 6 deletions pkg/printers/checkstyle.go
Expand Up @@ -4,10 +4,10 @@ import (
"context"
"encoding/xml"
"fmt"
"io"

"github.com/go-xmlfmt/xmlfmt"

"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result"
)

Expand All @@ -32,13 +32,15 @@ type checkstyleError struct {

const defaultCheckstyleSeverity = "error"

type Checkstyle struct{}
type Checkstyle struct {
w io.Writer
}

func NewCheckstyle() *Checkstyle {
return &Checkstyle{}
func NewCheckstyle(w io.Writer) *Checkstyle {
return &Checkstyle{w: w}
}

func (Checkstyle) Print(ctx context.Context, issues []result.Issue) error {
func (p Checkstyle) Print(ctx context.Context, issues []result.Issue) error {
out := checkstyleOutput{
Version: "5.0",
}
Expand Down Expand Up @@ -82,6 +84,10 @@ func (Checkstyle) Print(ctx context.Context, issues []result.Issue) error {
return err
}

fmt.Fprintf(logutils.StdOut, "%s%s\n", xml.Header, xmlfmt.FormatXML(string(data), "", " "))
_, err = fmt.Fprintf(p.w, "%s%s\n", xml.Header, xmlfmt.FormatXML(string(data), "", " "))
if err != nil {
return err
}

return nil
}
12 changes: 8 additions & 4 deletions pkg/printers/codeclimate.go
Expand Up @@ -4,8 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"io"

"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result"
)

Expand All @@ -24,10 +24,11 @@ type CodeClimateIssue struct {
}

type CodeClimate struct {
w io.Writer
}

func NewCodeClimate() *CodeClimate {
return &CodeClimate{}
func NewCodeClimate(w io.Writer) *CodeClimate {
return &CodeClimate{w: w}
}

func (p CodeClimate) Print(ctx context.Context, issues []result.Issue) error {
Expand All @@ -52,6 +53,9 @@ func (p CodeClimate) Print(ctx context.Context, issues []result.Issue) error {
return err
}

fmt.Fprint(logutils.StdOut, string(outputJSON))
_, err = fmt.Fprint(p.w, string(outputJSON))
if err != nil {
return err
}
return nil
}
11 changes: 6 additions & 5 deletions pkg/printers/github.go
Expand Up @@ -3,20 +3,21 @@ package printers
import (
"context"
"fmt"
"io"

"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result"
)

type github struct {
w io.Writer
}

const defaultGithubSeverity = "error"

// NewGithub output format outputs issues according to GitHub actions format:
// https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
func NewGithub() Printer {
return &github{}
func NewGithub(w io.Writer) Printer {
return &github{w: w}
}

// print each line as: ::error file=app.js,line=10,col=15::Something went wrong
Expand All @@ -35,9 +36,9 @@ func formatIssueAsGithub(issue *result.Issue) string {
return ret
}

func (g *github) Print(_ context.Context, issues []result.Issue) error {
func (p *github) Print(_ context.Context, issues []result.Issue) error {
for ind := range issues {
_, err := fmt.Fprintln(logutils.StdOut, formatIssueAsGithub(&issues[ind]))
_, err := fmt.Fprintln(p.w, formatIssueAsGithub(&issues[ind]))
if err != nil {
return err
}
Expand Down
14 changes: 8 additions & 6 deletions pkg/printers/html.go
Expand Up @@ -4,9 +4,9 @@ import (
"context"
"fmt"
"html/template"
"io"
"strings"

"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result"
)

Expand Down Expand Up @@ -123,13 +123,15 @@ type htmlIssue struct {
Code string
}

type HTML struct{}
type HTML struct {
w io.Writer
}

func NewHTML() *HTML {
return &HTML{}
func NewHTML(w io.Writer) *HTML {
return &HTML{w: w}
}

func (h HTML) Print(_ context.Context, issues []result.Issue) error {
func (p HTML) Print(_ context.Context, issues []result.Issue) error {
var htmlIssues []htmlIssue

for i := range issues {
Expand All @@ -151,5 +153,5 @@ func (h HTML) Print(_ context.Context, issues []result.Issue) error {
return err
}

return t.Execute(logutils.StdOut, struct{ Issues []htmlIssue }{Issues: htmlIssues})
return t.Execute(p.w, struct{ Issues []htmlIssue }{Issues: htmlIssues})
}
15 changes: 5 additions & 10 deletions pkg/printers/json.go
Expand Up @@ -3,20 +3,21 @@ package printers
import (
"context"
"encoding/json"
"fmt"
"io"

"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/report"
"github.com/golangci/golangci-lint/pkg/result"
)

type JSON struct {
rd *report.Data
w io.Writer
}

func NewJSON(rd *report.Data) *JSON {
func NewJSON(rd *report.Data, w io.Writer) *JSON {
return &JSON{
rd: rd,
w: w,
}
}

Expand All @@ -34,11 +35,5 @@ func (p JSON) Print(ctx context.Context, issues []result.Issue) error {
res.Issues = []result.Issue{}
}

outputJSON, err := json.Marshal(res)
if err != nil {
return err
}

fmt.Fprint(logutils.StdOut, string(outputJSON))
return nil
return json.NewEncoder(p.w).Encode(res)
}
11 changes: 6 additions & 5 deletions pkg/printers/junitxml.go
Expand Up @@ -3,9 +3,9 @@ package printers
import (
"context"
"encoding/xml"
"io"
"strings"

"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result"
)

Expand Down Expand Up @@ -35,13 +35,14 @@ type failureXML struct {
}

type JunitXML struct {
w io.Writer
}

func NewJunitXML() *JunitXML {
return &JunitXML{}
func NewJunitXML(w io.Writer) *JunitXML {
return &JunitXML{w: w}
}

func (JunitXML) Print(ctx context.Context, issues []result.Issue) error {
func (p JunitXML) Print(ctx context.Context, issues []result.Issue) error {
suites := make(map[string]testSuiteXML) // use a map to group by file

for ind := range issues {
Expand Down Expand Up @@ -70,7 +71,7 @@ func (JunitXML) Print(ctx context.Context, issues []result.Issue) error {
res.TestSuites = append(res.TestSuites, val)
}

enc := xml.NewEncoder(logutils.StdOut)
enc := xml.NewEncoder(p.w)
enc.Indent("", " ")
if err := enc.Encode(res); err != nil {
return err
Expand Down
6 changes: 4 additions & 2 deletions pkg/printers/tab.go
Expand Up @@ -15,12 +15,14 @@ import (
type Tab struct {
printLinterName bool
log logutils.Log
w io.Writer
}

func NewTab(printLinterName bool, log logutils.Log) *Tab {
func NewTab(printLinterName bool, log logutils.Log, w io.Writer) *Tab {
return &Tab{
printLinterName: printLinterName,
log: log,
w: w,
}
}

Expand All @@ -30,7 +32,7 @@ func (p Tab) SprintfColored(ca color.Attribute, format string, args ...interface
}

func (p *Tab) Print(ctx context.Context, issues []result.Issue) error {
w := tabwriter.NewWriter(logutils.StdOut, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(p.w, 0, 0, 2, ' ', 0)

for i := range issues {
p.printIssue(&issues[i], w)
Expand Down

0 comments on commit 36ad3b2

Please sign in to comment.