diff --git a/README.md b/README.md index 83eee5470746..06ac8954320e 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ Usage: golangci-lint run [flags] Flags: - --out-format string Format of output: colored-line-number|line-number|json|tab (default "colored-line-number") + --out-format string Format of output: colored-line-number|line-number|json|tab|checkstyle (default "colored-line-number") --print-issued-lines Print lines of code with issue (default true) --print-linter-name Print linter name in issue line (default true) --issues-exit-code int Exit code when issues were found (default 1) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 92d417e6d32d..d9e50947247b 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -253,6 +253,8 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error { format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName) case config.OutFormatTab: p = printers.NewTab(e.cfg.Output.PrintLinterName) + case config.OutFormatCheckstyle: + p = printers.NewCheckstyle() default: return fmt.Errorf("unknown output format %s", format) } diff --git a/pkg/config/config.go b/pkg/config/config.go index c2e4ad7204ba..261a9f8dfa87 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,9 +11,16 @@ const ( OutFormatLineNumber = "line-number" OutFormatColoredLineNumber = "colored-line-number" OutFormatTab = "tab" + OutFormatCheckstyle = "checkstyle" ) -var OutFormats = []string{OutFormatColoredLineNumber, OutFormatLineNumber, OutFormatJSON, OutFormatTab} +var OutFormats = []string{ + OutFormatColoredLineNumber, + OutFormatLineNumber, + OutFormatJSON, + OutFormatTab, + OutFormatCheckstyle, +} type ExcludePattern struct { Pattern string diff --git a/pkg/printers/checkstyle.go b/pkg/printers/checkstyle.go new file mode 100644 index 000000000000..587b6f9fd673 --- /dev/null +++ b/pkg/printers/checkstyle.go @@ -0,0 +1,78 @@ +package printers + +import ( + "context" + "encoding/xml" + "fmt" + + "github.com/golangci/golangci-lint/pkg/result" +) + +type checkstyleOutput struct { + XMLName xml.Name `xml:"checkstyle"` + Version string `xml:"version,attr"` + Files []*checkstyleFile `xml:"file"` +} + +type checkstyleFile struct { + Name string `xml:"name,attr"` + Errors []*checkstyleError `xml:"error"` +} + +type checkstyleError struct { + Column int `xml:"column,attr"` + Line int `xml:"line,attr"` + Message string `xml:"message,attr"` + Severity string `xml:"severity,attr"` + Source string `xml:"source,attr"` +} + +const defaultSeverity = "error" + +type Checkstyle struct{} + +func NewCheckstyle() *Checkstyle { + return &Checkstyle{} +} + +func (Checkstyle) Print(ctx context.Context, issues <-chan result.Issue) (bool, error) { + out := checkstyleOutput{ + Version: "5.0", + } + + files := map[string]*checkstyleFile{} + + for issue := range issues { + file, ok := files[issue.FilePath()] + if !ok { + file = &checkstyleFile{ + Name: issue.FilePath(), + } + + files[issue.FilePath()] = file + } + + newError := &checkstyleError{ + Column: issue.Column(), + Line: issue.Line(), + Message: issue.Text, + Source: issue.FromLinter, + Severity: defaultSeverity, + } + + file.Errors = append(file.Errors, newError) + } + + out.Files = make([]*checkstyleFile, 0, len(files)) + for _, file := range files { + out.Files = append(out.Files, file) + } + + data, err := xml.Marshal(&out) + if err != nil { + return false, err + } + + fmt.Fprintf(StdOut, "%s%s\n", xml.Header, data) + return len(files) > 0, nil +} diff --git a/pkg/result/issue.go b/pkg/result/issue.go index 4daa4e17281d..62c951472c0c 100644 --- a/pkg/result/issue.go +++ b/pkg/result/issue.go @@ -23,6 +23,10 @@ func (i Issue) Line() int { return i.Pos.Line } +func (i Issue) Column() int { + return i.Pos.Column +} + func (i Issue) GetLineRange() Range { if i.LineRange == nil { return Range{