diff --git a/README.md b/README.md index cc3dc1d358f7..e170e171fc4c 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ Usage: golangci-lint run [flags] Flags: - --out-format string Format of output: colored-line-number|line-number|json|tab|checkstyle|code-climate (default "colored-line-number") + --out-format string Format of output: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml (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 42faf7e2400a..521a1071cf51 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -372,6 +372,8 @@ func (e *Executor) createPrinter() (printers.Printer, error) { p = printers.NewCheckstyle() case config.OutFormatCodeClimate: p = printers.NewCodeClimate() + case config.OutFormatJunitXML: + p = printers.NewJunitXML() default: return nil, fmt.Errorf("unknown output format %s", format) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 720c5e312e2d..9a113fe6d892 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -14,6 +14,7 @@ const ( OutFormatTab = "tab" OutFormatCheckstyle = "checkstyle" OutFormatCodeClimate = "code-climate" + OutFormatJunitXML = "junit-xml" ) var OutFormats = []string{ @@ -23,6 +24,7 @@ var OutFormats = []string{ OutFormatTab, OutFormatCheckstyle, OutFormatCodeClimate, + OutFormatJunitXML, } type ExcludePattern struct { diff --git a/pkg/printers/junitxml.go b/pkg/printers/junitxml.go new file mode 100644 index 000000000000..99d354a009ee --- /dev/null +++ b/pkg/printers/junitxml.go @@ -0,0 +1,68 @@ +package printers + +import ( + "context" + "encoding/xml" + "strings" + + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/golangci-lint/pkg/result" +) + +type testSuitesXML struct { + XMLName xml.Name `xml:"testsuites"` + TestSuites []testSuiteXML +} + +type testSuiteXML struct { + XMLName xml.Name `xml:"testsuite"` + Suite string `xml:"name,attr"` + TestCases []testCaseXML `xml:"testcase"` +} + +type testCaseXML struct { + Name string `xml:"name,attr"` + ClassName string `xml:"classname,attr"` + Status string `xml:"status,attr"` +} + +type JunitXML struct { +} + +func NewJunitXML() *JunitXML { + return &JunitXML{} +} + +func (JunitXML) Print(ctx context.Context, issues <-chan result.Issue) error { + suites := make(map[string]testSuiteXML) // use a map to group-by "FromLinter" + + for i := range issues { + fromLinter := i.FromLinter + testSuite := suites[fromLinter] + testSuite.Suite = fromLinter + + var source string + for _, line := range i.SourceLines { + source += strings.TrimSpace(line) + "; " + } + tc := testCaseXML{Name: i.Text, + ClassName: i.Pos.String(), + Status: strings.TrimSuffix(source, "; "), + } + + testSuite.TestCases = append(testSuite.TestCases, tc) + suites[fromLinter] = testSuite + } + + var res testSuitesXML + for _, val := range suites { + res.TestSuites = append(res.TestSuites, val) + } + + enc := xml.NewEncoder(logutils.StdOut) + enc.Indent("", " ") + if err := enc.Encode(res); err != nil { + return err + } + return nil +}