Skip to content

Commit

Permalink
Add support for 'checkstyle' output (#209)
Browse files Browse the repository at this point in the history
* Add support for 'checkstyle' output

* improve code comments

* remove severity translation

* use xml encoder

* use xml encoder

* update checkstyle usage to show exact xml output

* use assert.PanicsWithError
  • Loading branch information
sebastien-rosset authored Jul 28, 2022
1 parent 9d4a722 commit e588a3e
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/snippets/woke.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ woke [globs ...] [flags]
--exit-1-on-failure Exit with exit code 1 on failures
-h, --help help for woke
--no-ignore Ignored files in .gitignore, .ignore, .wokeignore, .git/info/exclude, and inline ignores are processed
-o, --output string Output type [text,simple,github-actions,json,sonarqube] (default "text")
-o, --output string Output type [text,simple,github-actions,json,sonarqube,checkstyle] (default "text")
--stdin Read from stdin
```

Expand Down
20 changes: 19 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This option may not be used at the same time as [File Globs](#file-globs)

## Outputs

Options for output include text (default), simple, json, github-actions, or sonarqube format.
Options for output include text (default), simple, json, github-actions, sonarqube, or checkstyle format.
The following fields are supported, depending on format:

| Field | Description |
Expand Down Expand Up @@ -216,6 +216,24 @@ Format used to populate results into the popular [SonarQube](https://www.sonarqu
!!! note
`<sonarqubeseverity>` is mapped from severity, such that an error in `woke` is translated to a `MAJOR`, warning to a `MINOR`, and info to `INFO`

### Checkstyle

!!! example ""
`woke -o checkstyle`

Format used to populate results into the [Checkstyle](https://checkstyle.org/) XML format.

#### Structure

```xml
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="5.0">
<file name="filepath">
<error column="startcol" line="lineno" message="description" severity="severity" source="woke"></error>
</file>
</checkstyle>
```

## Exit Code

By default, `woke` will exit with a successful exit code when there are any rule failures.
Expand Down
83 changes: 83 additions & 0 deletions pkg/printer/checkstyle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package printer

import (
"encoding/xml"
"fmt"
"io"

"github.com/get-woke/woke/pkg/result"
)

// Checkstyle is a Checkstyle printer meant for use by a Checkstyle annotation
type Checkstyle struct {
writer io.Writer
encoder *xml.Encoder
}

// NewCheckstyle returns a new Checkstyle printer
func NewCheckstyle(w io.Writer) *Checkstyle {
return &Checkstyle{
writer: w,
encoder: xml.NewEncoder(w),
}
}

type File struct {
XMLName xml.Name `xml:"file"`
Name string `xml:"name,attr"`
Errors []Error `xml:"error"`
}
type Error struct {
XMLName xml.Name `xml:"error"`
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"`
}

func (p *Checkstyle) PrintSuccessExitMessage() bool {
return true
}

// Print prints in the format for Checkstyle.
// https://github.com/checkstyle/checkstyle
func (p *Checkstyle) Print(fs *result.FileResults) error {
var f File
f.Name = fs.Filename
for _, r := range fs.Results {
f.Errors = append(f.Errors, Error{
Column: r.GetStartPosition().Column,
Line: r.GetStartPosition().Line,
Message: r.Reason(),
Severity: r.GetSeverity().String(),
Source: "woke",
})
}
return p.encoder.Encode(f)
}

func (p *Checkstyle) Start() {
fmt.Fprint(p.writer, xml.Header)
p.encoder.Indent("", " ")
if err := p.encoder.EncodeToken(xml.StartElement{
Name: xml.Name{Local: "checkstyle"},
Attr: []xml.Attr{
{Name: xml.Name{Local: "version"}, Value: "5.0"},
},
}); err != nil {
panic(err)
}
if err := p.encoder.Flush(); err != nil {
panic(err)
}
}

func (p *Checkstyle) End() {
if err := p.encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: "checkstyle"}}); err != nil {
panic(err)
}
if err := p.encoder.Flush(); err != nil {
panic(err)
}
}
65 changes: 65 additions & 0 deletions pkg/printer/checkstyle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package printer

import (
"bytes"
"go/token"
"testing"

"github.com/get-woke/woke/pkg/result"
"github.com/get-woke/woke/pkg/rule"

"github.com/stretchr/testify/assert"
)

func TestFormatResultForCheckstyle(t *testing.T) {
testResults := result.FileResults{
Filename: "my/file",
Results: []result.Result{
result.LineResult{
Rule: &rule.TestRule,
Finding: "whitelist",
StartPosition: &token.Position{
Filename: "my/file",
Offset: 0,
Line: 5,
Column: 3,
},
EndPosition: &token.Position{
Filename: "my/file",
Offset: 0,
Line: 5,
Column: 12,
},
},
},
}
buf := new(bytes.Buffer)
p := NewCheckstyle(buf)
p.Start()
p.Print(&testResults)
p.End()
expected := `<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="5.0">
<file name="my/file">
<error column="3" line="5" message="` + "`" + `whitelist` + "`" + ` may be insensitive, use ` + "`" + `allowlist` + "`" + ` instead" severity="warning" source="woke"></error>
</file>
</checkstyle>`
assert.Equal(t, expected, buf.String())
}

func TestCheckstyle_Start(t *testing.T) {
buf := new(bytes.Buffer)
p := NewCheckstyle(buf)
p.Start()
got := buf.String()

expected := `<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="5.0">`
assert.Equal(t, expected, got)
}

func TestCheckstyle_End(t *testing.T) {
buf := new(bytes.Buffer)
p := NewCheckstyle(buf)
assert.PanicsWithError(t, "xml: end tag </checkstyle> without start tag", func() { p.End() })
}
7 changes: 7 additions & 0 deletions pkg/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const (
// OutFormatSonarQube is an output format supported by SonarQube
// https://docs.sonarqube.org/latest/analysis/generic-issue/
OutFormatSonarQube = "sonarqube"

// OutFormatCheckstyle outputs in checkstyle format.
// https://github.com/checkstyle/checkstyle
OutFormatCheckstyle = "checkstyle"
)

// OutFormats are all the available output formats. The first one should be the default
Expand All @@ -43,6 +47,7 @@ var OutFormats = []string{
OutFormatGitHubActions,
OutFormatJSON,
OutFormatSonarQube,
OutFormatCheckstyle,
}

// OutFormatsString is all OutFormats, as a comma-separated string
Expand All @@ -62,6 +67,8 @@ func NewPrinter(f string, w io.Writer) (Printer, error) {
p = NewJSON(w)
case OutFormatSonarQube:
p = NewSonarQube(w)
case OutFormatCheckstyle:
p = NewCheckstyle(w)
default:
return p, fmt.Errorf("%s is not a valid printer type", f)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestCreatePrinter(t *testing.T) {
{OutFormatGitHubActions, &GitHubActions{}},
{OutFormatJSON, &JSON{}},
{OutFormatSonarQube, &SonarQube{}},
{OutFormatCheckstyle, &Checkstyle{}},
}

for _, test := range tests {
Expand Down

0 comments on commit e588a3e

Please sign in to comment.