Skip to content

Commit

Permalink
Add JSON output type (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
caitlinelfring authored Oct 20, 2020
1 parent 84d282c commit 3dc4c1e
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Flags:
--exit-1-on-failure Exit with exit code 1 on failures
-h, --help help for woke
--no-ignore Files matching entries in .gitignore/.wokeignore are parsed
-o, --output string Output type [text,simple,github-actions] (default "text")
-o, --output string Output type [text,simple,github-actions,json] (default "text")
--stdin Read from stdin
-v, --version version for woke
```
Expand Down
28 changes: 28 additions & 0 deletions pkg/printer/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package printer

import (
"bytes"
"encoding/json"
"fmt"

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

// JSON is a JSON printer meant for a machine to read
type JSON struct{}

// NewJSON returns a new JSON printer
func NewJSON() *JSON {
return &JSON{}
}

// Print prints in FileResults as json
// NOTE: The JSON printer will bring each line result as a JSON string.
// It will not be presented as an array of FileResults. You will neeed to
// Split by new line to parse the full output
func (p *JSON) Print(fs *result.FileResults) error {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(fs)
fmt.Print(buf.String()) // json Encoder already puts a new line in, so no need for Println here
return err
}
17 changes: 17 additions & 0 deletions pkg/printer/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package printer

import (
"testing"

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

func TestJSON_Print(t *testing.T) {
p := NewJSON()
res := generateFileResult()
got := captureOutput(func() {
assert.NoError(t, p.Print(res))
})
expected := `{"Filename":"foo.txt","Results":[{"Rule":{"Name":"blacklist","Terms":["blacklist","black-list","blacklisted","black-listed"],"Alternatives":["denylist","blocklist"],"Note":"","Severity":"warning"},"Violation":"blacklist","Line":"this blacklist must change","StartPosition":{"Filename":"foo.txt","Offset":0,"Line":1,"Column":6},"EndPosition":{"Filename":"foo.txt","Offset":0,"Line":1,"Column":15},"Reason":"` + "`blacklist`" + ` may be insensitive, use ` + "`denylist`" + `, ` + "`blocklist`" + ` instead"}]}` + "\n"
assert.Equal(t, expected, got)
}
6 changes: 6 additions & 0 deletions pkg/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ const (
// OutFormatGitHubActions is an output format supported by GitHub Actions annotations
// https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
OutFormatGitHubActions = "github-actions"

// OutFormatJSON outputs in json
OutFormatJSON = "json"
)

// OutFormats are all the available output formats. The first one should be the default
var OutFormats = []string{
OutFormatText,
OutFormatSimple,
OutFormatGitHubActions,
OutFormatJSON,
}

// OutFormatsString is all OutFormats, as a comma-separated string
Expand All @@ -45,6 +49,8 @@ func NewPrinter(f string) (Printer, error) {
p = NewSimple()
case OutFormatGitHubActions:
p = NewGitHubActions()
case OutFormatJSON:
p = NewJSON()
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 @@ -14,6 +14,7 @@ func TestCreatePrinter(t *testing.T) {
{OutFormatSimple, &Simple{}},
{OutFormatText, &Text{}},
{OutFormatGitHubActions, &GitHubActions{}},
{OutFormatJSON, &JSON{}},
}

for _, test := range tests {
Expand Down
14 changes: 14 additions & 0 deletions pkg/result/lineresult.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package result

import (
"encoding/json"
"fmt"
"go/token"
"strings"
Expand Down Expand Up @@ -97,3 +98,16 @@ func (r LineResult) GetEndPosition() *token.Position { return r.EndPosition }

// GetLine returns the entire line for the LineResult
func (r LineResult) GetLine() string { return r.Line }

type jsonLineResult LineResult

// MarshalJSON override to include Reason in the json response
func (r LineResult) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
jsonLineResult
Reason string
}{
jsonLineResult: jsonLineResult(r),
Reason: r.Reason(),
})
}
38 changes: 38 additions & 0 deletions pkg/result/lineresult_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package result

import (
"fmt"
"go/token"
"testing"

"github.com/get-woke/woke/pkg/rule"
Expand All @@ -21,3 +22,40 @@ func TestFindResults(t *testing.T) {
rs = FindResults(&rule.WhitelistRule, "my/file", "this has the term whitelist #wokeignore:rule=whitelist", 1)
assert.Len(t, rs, 0)
}

func TestLineResult_MarshalJSON(t *testing.T) {
lr := testLineResult()
b, err := lr.MarshalJSON()
assert.NoError(t, err)
assert.Contains(t, string(b), fmt.Sprintf(`"Reason":"%s"`, lr.Reason()))
}

func TestLineResult_GetSeverity(t *testing.T) {
lr := testLineResult()
assert.Equal(t, lr.GetSeverity(), lr.Rule.Severity)
}

func TestLineResult_GetStartPosition(t *testing.T) {
lr := testLineResult()
assert.Equal(t, lr.GetStartPosition(), lr.StartPosition)
}

func TestLineResult_GetEndPosition(t *testing.T) {
lr := testLineResult()
assert.Equal(t, lr.GetEndPosition(), lr.EndPosition)
}

func TestLineResult_GetLine(t *testing.T) {
lr := testLineResult()
assert.Equal(t, lr.GetLine(), lr.Line)
}

func testLineResult() LineResult {
return LineResult{
Rule: &rule.WhitelistRule,
Violation: "whitelist",
Line: "whitelist",
StartPosition: &token.Position{Line: 1, Offset: 0},
EndPosition: &token.Position{Line: 1, Offset: 8},
}
}
10 changes: 10 additions & 0 deletions pkg/rule/severity.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package rule

import (
"encoding/json"

"github.com/fatih/color"
"gopkg.in/yaml.v2"
)
Expand Down Expand Up @@ -67,3 +69,11 @@ func (s *Severity) UnmarshalYAML(unmarshal func(interface{}) error) error {

return nil
}

// compile-time check that Severity satisfies the yaml Unmarshaler
var _ json.Marshaler = (*Severity)(nil)

// MarshalJSON to marshal Severity as a string
func (s *Severity) MarshalJSON() ([]byte, error) {
return []byte(`"` + s.String() + `"`), nil
}
24 changes: 24 additions & 0 deletions pkg/rule/severity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ func TestSeverity_UnmarshalYAML(t *testing.T) {
{"error", SevError},
{"info", SevInfo},
{"not-valid", SevInfo},
{"0", SevInfo},
{"1", SevInfo},
{"2", SevInfo},
{"3", SevInfo},
{"4", SevInfo},
{"99", SevInfo},
}
for _, test := range tests {
sev := new(Severity)
Expand All @@ -28,6 +34,24 @@ func TestSeverity_UnmarshalYAML(t *testing.T) {
}
}

func TestSeverity_MarshalJSON(t *testing.T) {
tests := []struct {
input Severity
expected string
}{
{SevWarn, `"warning"`},
{SevError, `"error"`},
{SevInfo, `"info"`},
}
for _, test := range tests {
sev := new(Severity)
b, err := test.input.MarshalJSON()
assert.NoError(t, err)

assert.Equalf(t, test.expected, string(b), "expected: %s, got: %s", test.expected, sev)
}
}

func TestSeverity_Colorize(t *testing.T) {
tests := []struct {
input Severity
Expand Down

0 comments on commit 3dc4c1e

Please sign in to comment.