Repository: reviewdog/reviewdog
Pull request #684 (2020-07-22 15:48:28)

github-check: Support Reviewdog Diagnostic Format
Support annotation level per result and range annotation.

- [ ] Updated Unreleased section in [CHANGELOG](https://github.com/reviewdog/reviewdog/blob/master/CHANGELOG.md) or it's not notable changes.


Patch:
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
index d32764f1..27245216 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/reviewdog.yml
@@ -41,6 +41,13 @@ jobs:
         run: |
           cd ./_testdata/ && golint ./... | reviewdog -f=golint -name=golint-check-subdir -reporter=github-check -level=info -filter-mode=nofilter
 
+      - name: Custom rdjsonl test (github-check)
+        env:
+          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          cat ./_testdata/custom_rdjsonl.json | \
+            reviewdog -name="custom-rdjsonl" -f=rdjsonl -reporter=github-check -level=info
+
   reviewdog-pr:
     if: github.event_name == 'pull_request'
     name: reviewdog on Pull Request
@@ -149,12 +156,12 @@ jobs:
             -reporter=local -diff='git diff master' || EXIT_CODE=$?
           test "${EXIT_CODE}" = 1
 
-      - name: Multiline
+      - name: Custom rdjsonl test (github-pr-review)
         env:
           REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
           cat ./_testdata/custom_rdjsonl.json | \
-            reviewdog -name="multiline-test" -f=rdjsonl -reporter=github-pr-review
+            reviewdog -name="custom-rdjsonl" -f=rdjsonl -reporter=github-pr-review
 
   golangci-lint:
     if: github.event_name == 'pull_request'
diff --git a/cmd/reviewdog/doghouse.go b/cmd/reviewdog/doghouse.go
index 192a8c60..e93793ae 100644
--- a/cmd/reviewdog/doghouse.go
+++ b/cmd/reviewdog/doghouse.go
@@ -179,12 +179,10 @@ func postResultSet(ctx context.Context, resultSet *reviewdog.ResultMap,
 }
 
 func checkResultToAnnotation(c *reviewdog.CheckResult, wd, gitRelWd string) *doghouse.Annotation {
-	loc := c.Diagnostic.GetLocation()
-	// TODO(haya14busa): Pass diagnostic to annotation instead.
+	c.Diagnostic.GetLocation().Path = filepath.ToSlash(filepath.Join(
+		gitRelWd, reviewdog.CleanPath(c.Diagnostic.GetLocation().GetPath(), wd)))
 	return &doghouse.Annotation{
-		Path:       filepath.ToSlash(filepath.Join(gitRelWd, reviewdog.CleanPath(loc.GetPath(), wd))),
-		Line:       int(loc.GetRange().GetStart().GetLine()),
-		Message:    c.Diagnostic.GetMessage(),
+		Diagnostic: c.Diagnostic,
 		RawMessage: strings.Join(c.Lines, "\n"),
 	}
 }
diff --git a/doghouse/server/doghouse.go b/doghouse/server/doghouse.go
index 7e45a032..6f360777 100644
--- a/doghouse/server/doghouse.go
+++ b/doghouse/server/doghouse.go
@@ -170,7 +170,20 @@ func (ch *Checker) conclusion() string {
 }
 
 // https://developer.github.com/v3/checks/runs/#annotations-object
-func (ch *Checker) annotationLevel() string {
+func (ch *Checker) annotationLevel(s rdf.Severity) string {
+	switch s {
+	case rdf.Severity_ERROR:
+		return "failure"
+	case rdf.Severity_WARNING:
+		return "warning"
+	case rdf.Severity_INFO:
+		return "notice"
+	default:
+		return ch.reqAnnotationLevel()
+	}
+}
+
+func (ch *Checker) reqAnnotationLevel() string {
 	switch strings.ToLower(ch.req.Level) {
 	case "info":
 		return "notice"
@@ -220,19 +233,35 @@ func (ch *Checker) summaryFindings(name string, checks []*reviewdog.FilteredChec
 
 func (ch *Checker) toCheckRunAnnotation(c *reviewdog.FilteredCheck) *github.CheckRunAnnotation {
 	loc := c.Diagnostic.GetLocation()
-	endLine := loc.GetRange().GetEnd().GetLine()
+	startLine := int(loc.GetRange().GetStart().GetLine())
+	endLine := int(loc.GetRange().GetEnd().GetLine())
 	if endLine == 0 {
-		endLine = loc.GetRange().GetStart().GetLine()
+		endLine = startLine
 	}
 	a := &github.CheckRunAnnotation{
 		Path:            github.String(loc.GetPath()),
-		StartLine:       github.Int(int(loc.GetRange().GetStart().GetLine())),
-		EndLine:         github.Int(int(endLine)),
-		AnnotationLevel: github.String(ch.annotationLevel()),
+		StartLine:       github.Int(startLine),
+		EndLine:         github.Int(endLine),
+		AnnotationLevel: github.String(ch.annotationLevel(c.Diagnostic.Severity)),
 		Message:         github.String(c.Diagnostic.GetMessage()),
 	}
-	if ch.req.Name != "" {
-		a.Title = github.String(fmt.Sprintf("[%s] %s#L%d", ch.req.Name, loc.GetPath(), loc.GetRange().GetStart().GetLine()))
+	// Annotations only support start_column and end_column on the same line.
+	if startLine == endLine {
+		if s, e := loc.GetRange().GetStart().GetColumn(), loc.GetRange().GetEnd().GetColumn(); s != 0 && e != 0 {
+			a.StartColumn = github.Int(int(s))
+			a.EndColumn = github.Int(int(e))
+		}
+	}
+	toolName := c.Diagnostic.GetSource().GetName()
+	if toolName == "" {
+		toolName = ch.req.Name
+	}
+	if toolName != "" {
+		line := fmt.Sprintf("L%d", startLine)
+		if startLine < endLine {
+			line += fmt.Sprintf("-L%d", endLine)
+		}
+		a.Title = github.String(fmt.Sprintf("[%s] %s#%s", toolName, loc.GetPath(), line))
 	}
 	if s := strings.Join(c.Lines, "\n"); s != "" {
 		a.RawDetails = github.String(s)
@@ -263,22 +292,33 @@ func (ch *Checker) rawPullRequestDiff(ctx context.Context, pr int) ([]byte, erro
 func annotationsToCheckResults(as []*doghouse.Annotation) []*reviewdog.CheckResult {
 	cs := make([]*reviewdog.CheckResult, 0, len(as))
 	for _, a := range as {
-		cs = append(cs, &reviewdog.CheckResult{
-			Diagnostic: &rdf.Diagnostic{
-				Location: &rdf.Location{
-					Path: a.Path,
-					Range: &rdf.Range{
-						Start: &rdf.Position{
-							Line: int32(a.Line),
-						},
+		cs = append(cs, annotationToCheckResult(a))
+	}
+	return cs
+}
+
+func annotationToCheckResult(a *doghouse.Annotation) *reviewdog.CheckResult {
+	if a.Diagnostic != nil {
+		return &reviewdog.CheckResult{
+			Diagnostic: a.Diagnostic,
+			Lines:      strings.Split(a.RawMessage, "\n"),
+		}
+	}
+	// Old reviwedog CLI doesn't have the Diagnostic field.
+	return &reviewdog.CheckResult{
+		Diagnostic: &rdf.Diagnostic{
+			Location: &rdf.Location{
+				Path: a.Path,
+				Range: &rdf.Range{
+					Start: &rdf.Position{
+						Line: int32(a.Line),
 					},
 				},
-				Message: a.Message,
 			},
-			Lines: strings.Split(a.RawMessage, "\n"),
-		})
+			Message: a.Message,
+		},
+		Lines: strings.Split(a.RawMessage, "\n"),
 	}
-	return cs
 }
 
 func min(x, y int) int {
diff --git a/doghouse/service.go b/doghouse/service.go
index ab1c9c93..b8c9630b 100644
--- a/doghouse/service.go
+++ b/doghouse/service.go
@@ -3,6 +3,7 @@ package doghouse
 import (
 	"github.com/reviewdog/reviewdog"
 	"github.com/reviewdog/reviewdog/difffilter"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 // CheckRequest represents doghouse GitHub check request.
@@ -74,18 +75,21 @@ type CheckResponse struct {
 
 // Annotation represents an annotation to file or specific line.
 type Annotation struct {
-	// Relative file path
-	// Required.
-	Path string `json:"path,omitempty"`
-	// Line number.
+	// Diagnostic.Location.Path must be relative path to the project root.
 	// Optional.
-	Line int `json:"line,omitempty"`
-	// Annotation message.
-	// Required.
-	Message string `json:"message,omitempty"`
+	Diagnostic *rdf.Diagnostic `json:"diagnostic,omitempty"`
+
 	// Original error message of this annotation.
 	// Optional.
 	RawMessage string `json:"raw_message,omitempty"`
 
-	// TODO(haya14busa): Support RDFormat.
+	// DEPRECATED fields below. Need to support them for the old reviewdog CLI
+	// version.
+
+	// DEPRECATED: Use Diagnostic.
+	Path string `json:"path,omitempty"`
+	// DEPRECATED: Use Diagnostic.
+	Line int `json:"line,omitempty"`
+	// DEPRECATED: Use Diagnostic.
+	Message string `json:"message,omitempty"`
 }


Test patch:
diff --git a/_testdata/custom.txt b/_testdata/custom.txt
index 9bda1ee0..32ebfb87 100644
--- a/_testdata/custom.txt
+++ b/_testdata/custom.txt
@@ -1,9 +1,13 @@
 Custom text for testing
 
-line 5
+Line 3
 
 --- Range
-	a = 1
-	b = 2
-	c = 3
+	a = 10
+	b = 20
+	c = 30
 ---
+
+1234567890
+haya14busa
+    ~~
diff --git a/_testdata/custom_rdjsonl.json b/_testdata/custom_rdjsonl.json
index 17026653..e5ff0d4e 100644
--- a/_testdata/custom_rdjsonl.json
+++ b/_testdata/custom_rdjsonl.json
@@ -1,6 +1,8 @@
-{"message":"multiline test 1","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5}, "end":{"line":9}}}}
-{"message":"multiline test 2 (same line)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5}, "end":{"line":5}}}}
-{"message":"multiline test 3 (with line-break)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":6,"column":2}, "end":{"line":7,"column":1}}}}
-{"message":"multiline test 4 (with line-break)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":6,"column":2}, "end":{"line":9,"column":1}}}}
-{"message":"severity test 1","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5, "column": 5}}}, "severity": "INFO"}
-{"message":"severity test 2","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5, "column": 4}}}, "severity": 2}
+{"message":"range test 1 (line only)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5}, "end":{"line":9}}}}
+{"message":"range test 2 (same line)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":5}, "end":{"line":5}}}}
+{"message":"range test 3 (with line-break)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":6,"column":2}, "end":{"line":7,"column":1}}}}
+{"message":"range test 4 (with line-break)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":6,"column":2}, "end":{"line":9,"column":1}}}}
+{"message":"range test 5 (within line)","location":{"path":"_testdata/custom.txt","range":{"start":{"line":12, "column": 5},"end":{"line":12, "column": 7}}}}
+{"message":"severity test 1","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 5}}}, "severity": "INFO"}
+{"message":"severity test 2","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3, "column": 4}}}, "severity": 2}
+{"source":{"name": "rdjsonl-source"}, "message":"source test","location":{"path":"_testdata/custom.txt","range":{"start":{"line":3}}}}
diff --git a/cmd/reviewdog/doghouse_test.go b/cmd/reviewdog/doghouse_test.go
index 8dcae649..737e63b3 100644
--- a/cmd/reviewdog/doghouse_test.go
+++ b/cmd/reviewdog/doghouse_test.go
@@ -234,26 +234,42 @@ func TestPostResultSet_withReportURL(t *testing.T) {
 		case "name1":
 			if diff := cmp.Diff(req.Annotations, []*doghouse.Annotation{
 				{
-					Line:       14,
-					Message:    "name1: test 1",
-					Path:       "cmd/reviewdog/reviewdog.go",
+					Diagnostic: &rdf.Diagnostic{
+						Message: "name1: test 1",
+						Location: &rdf.Location{
+							Path: "cmd/reviewdog/reviewdog.go",
+							Range: &rdf.Range{
+								Start: &rdf.Position{Line: 14},
+							},
+						},
+					},
 					RawMessage: "L1\nL2",
 				},
 				{
-					Message: "name1: test 2",
-					Path:    "cmd/reviewdog/reviewdog.go",
+					Diagnostic: &rdf.Diagnostic{
+						Message: "name1: test 2",
+						Location: &rdf.Location{
+							Path: "cmd/reviewdog/reviewdog.go",
+						},
+					},
 				},
-			}); diff != "" {
+			}, protocmp.Transform()); diff != "" {
 				t.Errorf("%s: req.Annotation have diff:\n%s", req.Name, diff)
 			}
 		case "name2":
 			if diff := cmp.Diff(req.Annotations, []*doghouse.Annotation{
 				{
-					Line:    14,
-					Message: "name2: test 1",
-					Path:    "cmd/reviewdog/doghouse.go",
+					Diagnostic: &rdf.Diagnostic{
+						Message: "name2: test 1",
+						Location: &rdf.Location{
+							Path: "cmd/reviewdog/doghouse.go",
+							Range: &rdf.Range{
+								Start: &rdf.Position{Line: 14},
+							},
+						},
+					},
 				},
-			}); diff != "" {
+			}, protocmp.Transform()); diff != "" {
 				t.Errorf("%s: req.Annotation have diff:\n%s", req.Name, diff)
 			}
 		default:
diff --git a/doghouse/server/doghouse_test.go b/doghouse/server/doghouse_test.go
index d28fd4ed..6ee58773 100644
--- a/doghouse/server/doghouse_test.go
+++ b/doghouse/server/doghouse_test.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/reviewdog/reviewdog/difffilter"
 	"github.com/reviewdog/reviewdog/doghouse"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 type fakeCheckerGitHubCli struct {
@@ -73,17 +74,96 @@ func TestCheck_OK(t *testing.T) {
 		SHA:         sha,
 		Annotations: []*doghouse.Annotation{
 			{
-				Path:       "sample.new.txt",
-				Line:       2,
-				Message:    "test message",
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test message",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2, Column: 1},
+						},
+					},
+				},
 				RawMessage: "raw test message",
 			},
 			{
-				Path:       "sample.new.txt",
-				Line:       14,
-				Message:    "test message outside diff",
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test message outside diff",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 14},
+						},
+					},
+				},
 				RawMessage: "raw test message outside diff",
 			},
+			{
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test multiline",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2},
+							End:   &rdf.Position{Line: 3},
+						},
+					},
+				},
+			},
+			{
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test multiline with column",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2, Column: 1},
+							End:   &rdf.Position{Line: 3, Column: 5},
+						},
+					},
+				},
+			},
+			{
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test range comment",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2, Column: 1},
+							End:   &rdf.Position{Line: 2, Column: 5},
+						},
+					},
+				},
+			},
+			{
+				Diagnostic: &rdf.Diagnostic{
+					Message:  "test severity override",
+					Severity: rdf.Severity_ERROR,
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2},
+						},
+					},
+				},
+			},
+			{
+				Diagnostic: &rdf.Diagnostic{
+					Message: "source test",
+					Source: &rdf.Source{
+						Name: "awesome-linter",
+					},
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2},
+						},
+					},
+				},
+			},
+			{
+				Path:    "sample.new.txt",
+				Line:    2,
+				Message: "request from old clients",
+			},
 		},
 		Level: "warning",
 	}
@@ -124,6 +204,56 @@ func TestCheck_OK(t *testing.T) {
 					Title:           github.String("[haya14busa-linter] sample.new.txt#L2"),
 					RawDetails:      github.String("raw test message"),
 				},
+				{
+					Path:            github.String("sample.new.txt"),
+					StartLine:       github.Int(2),
+					EndLine:         github.Int(3),
+					AnnotationLevel: github.String("warning"),
+					Message:         github.String("test multiline"),
+					Title:           github.String("[haya14busa-linter] sample.new.txt#L2-L3"),
+				},
+				{
+					Path:            github.String("sample.new.txt"),
+					StartLine:       github.Int(2),
+					EndLine:         github.Int(3),
+					AnnotationLevel: github.String("warning"),
+					Message:         github.String("test multiline with column"),
+					Title:           github.String("[haya14busa-linter] sample.new.txt#L2-L3"),
+				},
+				{
+					Path:            github.String("sample.new.txt"),
+					StartLine:       github.Int(2),
+					EndLine:         github.Int(2),
+					StartColumn:     github.Int(1),
+					EndColumn:       github.Int(5),
+					AnnotationLevel: github.String("warning"),
+					Message:         github.String("test range comment"),
+					Title:           github.String("[haya14busa-linter] sample.new.txt#L2"),
+				},
+				{
+					Path:            github.String("sample.new.txt"),
+					StartLine:       github.Int(2),
+					EndLine:         github.Int(2),
+					AnnotationLevel: github.String("failure"),
+					Message:         github.String("test severity override"),
+					Title:           github.String("[haya14busa-linter] sample.new.txt#L2"),
+				},
+				{
+					Path:            github.String("sample.new.txt"),
+					StartLine:       github.Int(2),
+					EndLine:         github.Int(2),
+					AnnotationLevel: github.String("warning"),
+					Message:         github.String("source test"),
+					Title:           github.String("[awesome-linter] sample.new.txt#L2"),
+				},
+				{
+					Path:            github.String("sample.new.txt"),
+					StartLine:       github.Int(2),
+					EndLine:         github.Int(2),
+					AnnotationLevel: github.String("warning"),
+					Message:         github.String("request from old clients"),
+					Title:           github.String("[haya14busa-linter] sample.new.txt#L2"),
+				},
 			}
 			if d := cmp.Diff(annotations, wantAnnotations); d != "" {
 				t.Errorf("Annotation diff found:\n%s", d)
@@ -162,15 +292,27 @@ func testOutsideDiff(t *testing.T, outsideDiff bool, filterMode difffilter.Mode)
 		SHA:         sha,
 		Annotations: []*doghouse.Annotation{
 			{
-				Path:       "sample.new.txt",
-				Line:       2,
-				Message:    "test message",
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test message",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2},
+						},
+					},
+				},
 				RawMessage: "raw test message",
 			},
 			{
-				Path:       "sample.new.txt",
-				Line:       14,
-				Message:    "test message outside diff",
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test message outside diff",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 14},
+						},
+					},
+				},
 				RawMessage: "raw test message outside diff",
 			},
 		},
@@ -256,9 +398,15 @@ func TestCheck_OK_multiple_update_runs(t *testing.T) {
 	}
 	for i := 0; i < 101; i++ {
 		req.Annotations = append(req.Annotations, &doghouse.Annotation{
-			Path:       "sample.new.txt",
-			Line:       2,
-			Message:    "test message",
+			Diagnostic: &rdf.Diagnostic{
+				Message: "test message",
+				Location: &rdf.Location{
+					Path: "sample.new.txt",
+					Range: &rdf.Range{
+						Start: &rdf.Position{Line: 2},
+					},
+				},
+			},
 			RawMessage: "raw test message",
 		})
 	}
@@ -317,15 +465,27 @@ func TestCheck_OK_nonPullRequests(t *testing.T) {
 		SHA:   sha,
 		Annotations: []*doghouse.Annotation{
 			{
-				Path:       "sample.new.txt",
-				Line:       2,
-				Message:    "test message",
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test message",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 2},
+						},
+					},
+				},
 				RawMessage: "raw test message",
 			},
 			{
-				Path:       "sample.new.txt",
-				Line:       14,
-				Message:    "test message2",
+				Diagnostic: &rdf.Diagnostic{
+					Message: "test message2",
+					Location: &rdf.Location{
+						Path: "sample.new.txt",
+						Range: &rdf.Range{
+							Start: &rdf.Position{Line: 14},
+						},
+					},
+				},
 				RawMessage: "raw test message2",
 			},
 		},