From 620eaaaeeb7b88f4cbb812cb33f866d28c91cdc9 Mon Sep 17 00:00:00 2001 From: haya14busa Date: Mon, 1 Jul 2024 02:14:55 +0900 Subject: [PATCH 1/6] Support -reporter=sarif --- cmd/reviewdog/main.go | 10 +++ comment_iowriter.go | 134 +++++++++++++++++++++++++++++++++++++++ comment_iowriter_test.go | 123 +++++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) diff --git a/cmd/reviewdog/main.go b/cmd/reviewdog/main.go index c3509f11..d5c2a5a8 100644 --- a/cmd/reviewdog/main.go +++ b/cmd/reviewdog/main.go @@ -100,6 +100,9 @@ const ( "rdjsonl" Report results to stdout in rdjsonl format. + "sarif" + Report results to stdout in SARIF format. + "github-check" Report results to GitHub Check. It works both for Pull Requests and commits. For Pull Request, you can see report results in GitHub PullRequest Check @@ -452,6 +455,13 @@ func run(r io.Reader, w io.Writer, opt *option) error { } ds = d cs = reviewdog.NewRDJSONLCommentWriter(w) + case "sarif": + d, err := localDiffService(opt) + if err != nil { + return err + } + ds = d + cs = reviewdog.NewSARIFCommentWriter(w, toolName(opt)) } if isProject { diff --git a/comment_iowriter.go b/comment_iowriter.go index 9e043068..6f8cdbdf 100644 --- a/comment_iowriter.go +++ b/comment_iowriter.go @@ -2,9 +2,11 @@ package reviewdog import ( "context" + "encoding/json" "fmt" "io" + "github.com/haya14busa/go-sarif/sarif" "github.com/reviewdog/reviewdog/proto/rdf" "google.golang.org/protobuf/encoding/protojson" ) @@ -142,3 +144,135 @@ func (cw *RDJSONCommentWriter) Flush(_ context.Context) error { } return nil } + +var _ CommentService = &SARIFCommentWriter{} + +// SARIFCommentWriter +type SARIFCommentWriter struct { + w io.Writer + comments []*Comment + toolName string +} + +func NewSARIFCommentWriter(w io.Writer, toolName string) *SARIFCommentWriter { + return &SARIFCommentWriter{w: w, toolName: toolName} +} + +func (cw *SARIFCommentWriter) Post(_ context.Context, c *Comment) error { + cw.comments = append(cw.comments, c) + return nil +} + +func (cw *SARIFCommentWriter) Flush(_ context.Context) error { + run := sarif.Run{ + Tool: sarif.Tool{ + Driver: sarif.ToolComponent{ + Name: cw.toolName, + }, + }, + } + rules := make(map[string]sarif.ReportingDescriptor) + for _, c := range cw.comments { + result := sarif.Result{ + Message: sarif.Message{ + Text: sarif.String(c.Result.Diagnostic.Message), + }, + } + if code := c.Result.Diagnostic.GetCode(); code.GetValue() != "" { + result.RuleID = sarif.String(code.GetValue()) + rules[code.GetValue()] = sarif.ReportingDescriptor{ + ID: code.GetValue(), + HelpURI: sarif.String(code.GetUrl()), + } + } + level := severity2level(c.Result.Diagnostic.GetSeverity()) + if level != sarif.None { + result.Level = &level + } + artifactLoc := sarif.ArtifactLocation{ + URI: sarif.String(c.Result.Diagnostic.GetLocation().GetPath()), + } + result.Locations = []sarif.Location{{ + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &artifactLoc, + Region: range2region(c.Result.Diagnostic.GetLocation().GetRange()), + }, + }} + if len(c.Result.Diagnostic.GetSuggestions()) > 0 { + result.Fixes = make([]sarif.Fix, 0) + for _, suggestion := range c.Result.Diagnostic.GetSuggestions() { + result.Fixes = append(result.Fixes, sarif.Fix{ + ArtifactChanges: []sarif.ArtifactChange{ + { + ArtifactLocation: artifactLoc, + Replacements: []sarif.Replacement{{ + DeletedRegion: *range2region(suggestion.GetRange()), + InsertedContent: &sarif.ArtifactContent{ + Text: sarif.String(suggestion.GetText()), + }, + }}, + }, + }, + }) + } + } + if len(c.Result.Diagnostic.GetRelatedLocations()) > 0 { + result.RelatedLocations = make([]sarif.Location, 0) + for _, relLoc := range c.Result.Diagnostic.GetRelatedLocations() { + result.RelatedLocations = append(result.RelatedLocations, sarif.Location{ + PhysicalLocation: &sarif.PhysicalLocation{ + ArtifactLocation: &sarif.ArtifactLocation{ + URI: sarif.String(relLoc.GetLocation().GetPath()), + }, + Region: range2region(relLoc.GetLocation().GetRange()), + }, + Message: &sarif.Message{ + Text: sarif.String(relLoc.Message), + }, + }) + } + } + run.Results = append(run.Results, result) + } + slf := sarif.NewSarif() + run.Tool.Driver.Rules = make([]sarif.ReportingDescriptor, 0, len(rules)) + for _, r := range rules { + run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, r) + } + slf.Runs = []sarif.Run{run} + encoder := json.NewEncoder(cw.w) + encoder.SetIndent("", " ") + return encoder.Encode(slf) +} + +func range2region(rng *rdf.Range) *sarif.Region { + region := &sarif.Region{} + start := rng.GetStart() + end := rng.GetEnd() + if start.GetLine() > 0 { + region.StartLine = sarif.Int64(int64(start.GetLine())) + } + if start.GetColumn() > 0 { + // Column is not usually unicodeCodePoints, but let's just keep it + // as is... + region.StartColumn = sarif.Int64(int64(start.GetColumn())) + } + if end.GetLine() > 0 { + region.EndLine = sarif.Int64(int64(end.GetLine())) + } + if end.GetColumn() > 0 { + region.EndColumn = sarif.Int64(int64(end.GetColumn())) + } + return region +} + +func severity2level(s rdf.Severity) sarif.Level { + switch s { + case rdf.Severity_ERROR: + return sarif.Error + case rdf.Severity_WARNING: + return sarif.Warning + default: + return sarif.None + } +} diff --git a/comment_iowriter_test.go b/comment_iowriter_test.go index 1ab06fde..8aeaf502 100644 --- a/comment_iowriter_test.go +++ b/comment_iowriter_test.go @@ -259,3 +259,126 @@ func TestRDJSONCommentWriter_Post(t *testing.T) { t.Errorf("got\n%v\nwant:\n%v", got, want) } } + +func TestSARIFCommentWriter_Post(t *testing.T) { + comments := []*Comment{ + { + Result: &filter.FilteredDiagnostic{ + Diagnostic: &rdf.Diagnostic{ + Location: &rdf.Location{Path: "/path/to/file"}, + Message: "message", + }, + }, + ToolName: "tool name", + }, + { + Result: &filter.FilteredDiagnostic{ + Diagnostic: &rdf.Diagnostic{ + Location: &rdf.Location{ + Path: "/path/to/file", + Range: &rdf.Range{Start: &rdf.Position{ + Column: 14, + }}, + }, + Message: "message", + }, + }, + }, + { + Result: &filter.FilteredDiagnostic{ + Diagnostic: &rdf.Diagnostic{ + Location: &rdf.Location{ + Path: "/path/to/file", + Range: &rdf.Range{Start: &rdf.Position{ + Column: 14, + }}, + }, + Message: "message", + Source: &rdf.Source{ + Name: "tool name in Diagnostic", + Url: "tool url", + }, + }, + }, + }, + } + buf := new(bytes.Buffer) + cw := NewSARIFCommentWriter(buf, "tool name [constructor]") + for _, c := range comments { + if err := cw.Post(context.Background(), c); err != nil { + t.Error(err) + } + } + if err := cw.Flush(context.Background()); err != nil { + t.Error(err) + } + want := ` +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "results": [ + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/path/to/file" + }, + "region": {} + } + } + ], + "message": { + "text": "message" + } + }, + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/path/to/file" + }, + "region": { + "startColumn": 14 + } + } + } + ], + "message": { + "text": "message" + } + }, + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/path/to/file" + }, + "region": { + "startColumn": 14 + } + } + } + ], + "message": { + "text": "message" + } + } + ], + "tool": { + "driver": { + "name": "tool name [constructor]" + } + } + } + ], + "version": "2.1.0" +}` + got := strings.TrimSpace(buf.String()) + if got != strings.TrimSpace(want) { + t.Errorf("got\n%v\nwant:\n%v", got, want) + } +} From cf3a4e78636036f22d8d60408148b67365c6faa6 Mon Sep 17 00:00:00 2001 From: haya14busa Date: Mon, 1 Jul 2024 02:23:40 +0900 Subject: [PATCH 2/6] Add sarif workflow --- .github/workflows/reviewdog.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index a20265cc..32b5b67e 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -233,6 +233,28 @@ jobs: cat ./_testdata/custom_rdjson.json | \ reviewdog -name="custom-rdjson" -f=rdjson -reporter=github-pr-annotations + reviewdog-sarif: + permissions: + contents: read + name: reviewdog (sarif) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + - name: Setup reviewdog + # uses: reviewdog/action-setup@v1 + run: | + go install ./cmd/reviewdog + - name: Custom rdjson test + run: | + mkdir ../results + cat ./_testdata/custom_rdjson.json | + reviewdog -name="custom-rdjson" -f=rdjson -reporter=sarif | + tee ../results/custom-rdjson.sarif + - uses: github/codeql-action/upload-sarif@v3 + typos: name: runner / typos runs-on: ubuntu-latest From ab05b99ddba8b1068e4c448ad0d2353cdb7beb2e Mon Sep 17 00:00:00 2001 From: haya14busa Date: Mon, 1 Jul 2024 02:31:44 +0900 Subject: [PATCH 3/6] Add golint.sarif --- .github/workflows/reviewdog.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index 32b5b67e..0c549f90 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -253,6 +253,12 @@ jobs: cat ./_testdata/custom_rdjson.json | reviewdog -name="custom-rdjson" -f=rdjson -reporter=sarif | tee ../results/custom-rdjson.sarif + - name: Install linters + run: go install golang.org/x/lint/golint@latest + - name: Run golint + run: | + golint ./... | reviewdog -f=golint -reporter=sarif | + tee ../results/golint.sarif - uses: github/codeql-action/upload-sarif@v3 typos: From 0e372da7362addf9a807b813c09ac350856d910c Mon Sep 17 00:00:00 2001 From: haya14busa Date: Mon, 1 Jul 2024 03:20:12 +0900 Subject: [PATCH 4/6] Update CHANGELOG --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 010a9046..e0adc1f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 outdated comments in github-pr-review reporter. Note that it won't delete comments if there is a reply considering there can be a meaningful discussion. -- [#1806](https://github.com/reviewdog/reviewdog/pull/1806) Add -reporter=rdjson/rdjsonl which outputs rdjson/rdjsonl format to stdout. It also changes the default behavior of -diff and -filter-mode for local reporters. If -filter-mode is not provided (-filter-mode=default) and -diff flag is not provided, reviewdog automatically set -filter-mode=nofilter. +- [#1806](https://github.com/reviewdog/reviewdog/pull/1806) Add + -reporter=rdjson/rdjsonl which outputs rdjson/rdjsonl format to stdout. It + also changes the default behavior of -diff and -filter-mode for local + reporters. If -filter-mode is not provided (-filter-mode=default) and -diff + flag is not provided, reviewdog automatically set -filter-mode=nofilter. +- [#1807](https://github.com/reviewdog/reviewdog/pull/1807) Add -reporter=sarif + which outputs SARIF format to stdout. You can upload the output SARIF to + GitHub and see code scanning alerts. + ### :bug: Fixes - ... From 907b2c0d5e4fcc98aed49d09ca3426b306e1dd18 Mon Sep 17 00:00:00 2001 From: haya14busa Date: Mon, 1 Jul 2024 21:27:38 +0900 Subject: [PATCH 5/6] use seenRules --- comment_iowriter.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/comment_iowriter.go b/comment_iowriter.go index 6f8cdbdf..ca4c8b6c 100644 --- a/comment_iowriter.go +++ b/comment_iowriter.go @@ -171,7 +171,7 @@ func (cw *SARIFCommentWriter) Flush(_ context.Context) error { }, }, } - rules := make(map[string]sarif.ReportingDescriptor) + seenRules := make(map[string]bool) for _, c := range cw.comments { result := sarif.Result{ Message: sarif.Message{ @@ -180,9 +180,12 @@ func (cw *SARIFCommentWriter) Flush(_ context.Context) error { } if code := c.Result.Diagnostic.GetCode(); code.GetValue() != "" { result.RuleID = sarif.String(code.GetValue()) - rules[code.GetValue()] = sarif.ReportingDescriptor{ - ID: code.GetValue(), - HelpURI: sarif.String(code.GetUrl()), + if seen := seenRules[code.GetValue()]; !seen { + seenRules[code.GetValue()] = true + run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, sarif.ReportingDescriptor{ + ID: code.GetValue(), + HelpURI: sarif.String(code.GetUrl()), + }) } } level := severity2level(c.Result.Diagnostic.GetSeverity()) @@ -235,10 +238,6 @@ func (cw *SARIFCommentWriter) Flush(_ context.Context) error { run.Results = append(run.Results, result) } slf := sarif.NewSarif() - run.Tool.Driver.Rules = make([]sarif.ReportingDescriptor, 0, len(rules)) - for _, r := range rules { - run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, r) - } slf.Runs = []sarif.Run{run} encoder := json.NewEncoder(cw.w) encoder.SetIndent("", " ") From 17fea07922fa7544c89d4d65d9d4437947b84484 Mon Sep 17 00:00:00 2001 From: haya14busa Date: Mon, 1 Jul 2024 21:34:29 +0900 Subject: [PATCH 6/6] fix Rule URL --- comment_iowriter.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/comment_iowriter.go b/comment_iowriter.go index ca4c8b6c..e69330cf 100644 --- a/comment_iowriter.go +++ b/comment_iowriter.go @@ -167,7 +167,8 @@ func (cw *SARIFCommentWriter) Flush(_ context.Context) error { run := sarif.Run{ Tool: sarif.Tool{ Driver: sarif.ToolComponent{ - Name: cw.toolName, + Name: cw.toolName, + Rules: make([]sarif.ReportingDescriptor, 0), }, }, } @@ -182,10 +183,13 @@ func (cw *SARIFCommentWriter) Flush(_ context.Context) error { result.RuleID = sarif.String(code.GetValue()) if seen := seenRules[code.GetValue()]; !seen { seenRules[code.GetValue()] = true - run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, sarif.ReportingDescriptor{ - ID: code.GetValue(), - HelpURI: sarif.String(code.GetUrl()), - }) + rd := sarif.ReportingDescriptor{ + ID: code.GetValue(), + } + if code.GetUrl() != "" { + rd.HelpURI = sarif.String(code.GetUrl()) + } + run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, rd) } } level := severity2level(c.Result.Diagnostic.GetSeverity())