Repository: reviewdog/reviewdog
Pull request #673 (2020-07-20 05:45:56)

Use Reviewdog Diagnostic Format internally
- [x] Updated Unreleased section in [CHANGELOG](https://github.com/reviewdog/reviewdog/blob/master/CHANGELOG.md) or it's not notable changes.




Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 059bd385..7a6e493d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 
 ### :book: Release Note <!-- optional -->
-<!-- TODO(haya14busa): Write about Reviewdog Diagnostic Format -->
 
 ### :rocket: Enhancements
-- [#629](https://github.com/reviewdog/reviewdog/pull/629) Introduced Reviewdog Diagnostic Format. ([@haya14busa])
 - ...
 
 ### :bug: Fixes
diff --git a/cmd/reviewdog/doghouse.go b/cmd/reviewdog/doghouse.go
index ea5612b8..192a8c60 100644
--- a/cmd/reviewdog/doghouse.go
+++ b/cmd/reviewdog/doghouse.go
@@ -179,10 +179,12 @@ 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.
 	return &doghouse.Annotation{
-		Path:       filepath.ToSlash(filepath.Join(gitRelWd, reviewdog.CleanPath(c.Path, wd))),
-		Line:       c.Lnum,
-		Message:    c.Message,
+		Path:       filepath.ToSlash(filepath.Join(gitRelWd, reviewdog.CleanPath(loc.GetPath(), wd))),
+		Line:       int(loc.GetRange().GetStart().GetLine()),
+		Message:    c.Diagnostic.GetMessage(),
 		RawMessage: strings.Join(c.Lines, "\n"),
 	}
 }
diff --git a/cmd/reviewdog/main.go b/cmd/reviewdog/main.go
index b8b78076..2900d564 100644
--- a/cmd/reviewdog/main.go
+++ b/cmd/reviewdog/main.go
@@ -19,7 +19,7 @@ import (
 	"golang.org/x/net/context" // "context"
 	"golang.org/x/oauth2"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 	"github.com/mattn/go-shellwords"
 	"github.com/reviewdog/errorformat/fmts"
 	"github.com/xanzy/go-gitlab"
diff --git a/comment_iowriter.go b/comment_iowriter.go
index c04fce01..40f80b02 100644
--- a/comment_iowriter.go
+++ b/comment_iowriter.go
@@ -43,11 +43,13 @@ func NewUnifiedCommentWriter(w io.Writer) *UnifiedCommentWriter {
 }
 
 func (mc *UnifiedCommentWriter) Post(_ context.Context, c *Comment) error {
-	s := c.Result.Path
-	if c.Result.Lnum > 0 {
-		s += fmt.Sprintf(":%d", c.Result.Lnum)
-		if c.Result.Col > 0 {
-			s += fmt.Sprintf(":%d", c.Result.Col)
+	loc := c.Result.Diagnostic.GetLocation()
+	s := loc.GetPath()
+	start := loc.GetRange().GetStart()
+	if start.GetLine() > 0 {
+		s += fmt.Sprintf(":%d", start.GetLine())
+		if start.GetColumn() > 0 {
+			s += fmt.Sprintf(":%d", start.GetColumn())
 		}
 	}
 	s += fmt.Sprintf(": [%s] %s", c.ToolName, c.Body)
diff --git a/doghouse/appengine/github.go b/doghouse/appengine/github.go
index 969c6151..38fd51f8 100644
--- a/doghouse/appengine/github.go
+++ b/doghouse/appengine/github.go
@@ -14,7 +14,7 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 	"github.com/justinas/nosurf"
 	"golang.org/x/oauth2"
 
diff --git a/doghouse/appengine/github_webhook.go b/doghouse/appengine/github_webhook.go
index 1ba7eb44..4803ba5e 100644
--- a/doghouse/appengine/github_webhook.go
+++ b/doghouse/appengine/github_webhook.go
@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 
 	"github.com/reviewdog/reviewdog/doghouse/server/storage"
 )
diff --git a/doghouse/client/github_client.go b/doghouse/client/github_client.go
index 660a6ad3..5158c6ce 100644
--- a/doghouse/client/github_client.go
+++ b/doghouse/client/github_client.go
@@ -3,7 +3,7 @@ package client
 import (
 	"context"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 
 	"github.com/reviewdog/reviewdog/doghouse"
 	"github.com/reviewdog/reviewdog/doghouse/server"
diff --git a/doghouse/server/doghouse.go b/doghouse/server/doghouse.go
index d1ca7740..51edbcab 100644
--- a/doghouse/server/doghouse.go
+++ b/doghouse/server/doghouse.go
@@ -8,12 +8,13 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 
 	"github.com/reviewdog/reviewdog"
 	"github.com/reviewdog/reviewdog/diff"
 	"github.com/reviewdog/reviewdog/difffilter"
 	"github.com/reviewdog/reviewdog/doghouse"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 	"github.com/reviewdog/reviewdog/service/github/githubutils"
 )
 
@@ -218,15 +219,20 @@ 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()
+	if endLine == 0 {
+		endLine = loc.GetRange().GetStart().GetLine()
+	}
 	a := &github.CheckRunAnnotation{
-		Path:            github.String(c.Path),
-		StartLine:       github.Int(c.Lnum),
-		EndLine:         github.Int(c.Lnum),
+		Path:            github.String(loc.GetPath()),
+		StartLine:       github.Int(int(loc.GetRange().GetStart().GetLine())),
+		EndLine:         github.Int(int(endLine)),
 		AnnotationLevel: github.String(ch.annotationLevel()),
-		Message:         github.String(c.Message),
+		Message:         github.String(c.Diagnostic.GetMessage()),
 	}
 	if ch.req.Name != "" {
-		a.Title = github.String(fmt.Sprintf("[%s] %s#L%d", ch.req.Name, c.Path, c.Lnum))
+		a.Title = github.String(fmt.Sprintf("[%s] %s#L%d", ch.req.Name, loc.GetPath(), loc.GetRange().GetStart().GetLine()))
 	}
 	if s := strings.Join(c.Lines, "\n"); s != "" {
 		a.RawDetails = github.String(s)
@@ -258,10 +264,18 @@ func annotationsToCheckResults(as []*doghouse.Annotation) []*reviewdog.CheckResu
 	cs := make([]*reviewdog.CheckResult, 0, len(as))
 	for _, a := range as {
 		cs = append(cs, &reviewdog.CheckResult{
-			Path:    a.Path,
-			Lnum:    a.Line,
-			Message: a.Message,
-			Lines:   strings.Split(a.RawMessage, "\n"),
+			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"),
 		})
 	}
 	return cs
diff --git a/doghouse/server/github.go b/doghouse/server/github.go
index ee032c11..08503930 100644
--- a/doghouse/server/github.go
+++ b/doghouse/server/github.go
@@ -6,7 +6,7 @@ import (
 	"net/http"
 
 	"github.com/bradleyfalzon/ghinstallation"
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 )
 
 type NewGitHubClientOption struct {
diff --git a/doghouse/server/github_checker.go b/doghouse/server/github_checker.go
index 88dd28ed..1c55ecb5 100644
--- a/doghouse/server/github_checker.go
+++ b/doghouse/server/github_checker.go
@@ -3,7 +3,7 @@ package server
 import (
 	"context"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 )
 
 type checkerGitHubClientInterface interface {
diff --git a/doghouse/service.go b/doghouse/service.go
index 35b7fcbf..ab1c9c93 100644
--- a/doghouse/service.go
+++ b/doghouse/service.go
@@ -86,4 +86,6 @@ type Annotation struct {
 	// Original error message of this annotation.
 	// Optional.
 	RawMessage string `json:"raw_message,omitempty"`
+
+	// TODO(haya14busa): Support RDFormat.
 }
diff --git a/filter.go b/filter.go
index 9a645fe6..94fe3994 100644
--- a/filter.go
+++ b/filter.go
@@ -25,15 +25,17 @@ func FilterCheck(results []*CheckResult, diff []*diff.FileDiff, strip int,
 	df := difffilter.New(diff, strip, cwd, mode)
 	for _, result := range results {
 		check := &FilteredCheck{CheckResult: result}
-		shouldReport, difffile, diffline := df.ShouldReport(result.Path, result.Lnum)
+		loc := result.Diagnostic.GetLocation()
+		lnum := int(loc.GetRange().GetStart().GetLine())
+		shouldReport, difffile, diffline := df.ShouldReport(loc.GetPath(), lnum)
 		check.ShouldReport = shouldReport
 		if diffline != nil {
 			check.LnumDiff = diffline.LnumDiff
 		}
-		result.Path = CleanPath(result.Path, cwd)
+		loc.Path = CleanPath(loc.GetPath(), cwd)
 		if difffile != nil {
 			check.InDiffFile = true
-			check.OldPath, check.OldLine = getOldPosition(difffile, strip, result.Path, result.Lnum)
+			check.OldPath, check.OldLine = getOldPosition(difffile, strip, loc.GetPath(), lnum)
 		}
 		checks = append(checks, check)
 	}
diff --git a/go.mod b/go.mod
index 739e9c1b..4aa897cf 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
 	github.com/bradleyfalzon/ghinstallation v1.1.1
 	github.com/golang/protobuf v1.4.2
 	github.com/google/go-cmp v0.5.0
-	github.com/google/go-github/v32 v32.1.0
+	github.com/google/go-github/v31 v31.0.0
 	github.com/haya14busa/go-actions-toolkit v0.0.0-20200105081403-ca0307860f01
 	github.com/haya14busa/secretbox v0.0.0-20180525171038-07c7ecf409f5
 	github.com/justinas/nosurf v1.1.0
diff --git a/go.sum b/go.sum
index ea898553..5860189c 100644
--- a/go.sum
+++ b/go.sum
@@ -127,8 +127,8 @@ github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4r
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
 github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
-github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
-github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
+github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo=
+github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
diff --git a/parser.go b/parser.go
index 9adda082..b4e41bbf 100644
--- a/parser.go
+++ b/parser.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/reviewdog/errorformat"
 	"github.com/reviewdog/errorformat/fmts"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 // ParserOpt represents option to create Parser. Either FormatName or
@@ -71,11 +72,19 @@ func (p *ErrorformatParser) Parse(r io.Reader) ([]*CheckResult, error) {
 		e := s.Entry()
 		if e.Valid {
 			rs = append(rs, &CheckResult{
-				Path:    e.Filename,
-				Lnum:    e.Lnum,
-				Col:     e.Col,
-				Message: e.Text,
-				Lines:   e.Lines,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: e.Filename,
+						Range: &rdf.Range{
+							Start: &rdf.Position{
+								Line:   int32(e.Lnum),
+								Column: int32(e.Col),
+							},
+						},
+					},
+					Message: e.Text,
+				},
+				Lines: e.Lines,
 			})
 		}
 	}
@@ -101,10 +110,18 @@ func (p *CheckStyleParser) Parse(r io.Reader) ([]*CheckResult, error) {
 	for _, file := range cs.Files {
 		for _, cerr := range file.Errors {
 			rs = append(rs, &CheckResult{
-				Path:    file.Name,
-				Lnum:    cerr.Line,
-				Col:     cerr.Column,
-				Message: cerr.Message,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: file.Name,
+						Range: &rdf.Range{
+							Start: &rdf.Position{
+								Line:   int32(cerr.Line),
+								Column: int32(cerr.Column),
+							},
+						},
+					},
+					Message: cerr.Message,
+				},
 				Lines: []string{
 					fmt.Sprintf("%v:%d:%d: %v: %v (%v)",
 						file.Name, cerr.Line, cerr.Column, cerr.Severity, cerr.Message, cerr.Source),
diff --git a/proto/rdf/README.md b/proto/rdf/README.md
index e55d2f75..22d78d9c 100644
--- a/proto/rdf/README.md
+++ b/proto/rdf/README.md
@@ -116,9 +116,6 @@ Example:
 
 ## Background: Still No Good Standard Diagnostic Format Out There in 2020
 
-Update: Found *The Static Analysis Results Interchange Format (SARIF)* as a
-potential good standard format.
-
 As of writing (2020), most diagnostic tools such as linters or compilers output
 results with their own format. Some tools support machine-readable structured
 format like their own JSON format, and other tools just support unstructured
@@ -160,45 +157,6 @@ format, but not everyone wants to support it.
 There are altenative solutions out there (which are not used by reviewdog) as
 well.
 
-### The Static Analysis Results Interchange Format (SARIF)
-[The Static Analysis Results Interchange Format (SARIF)](https://sarifweb.azurewebsites.net/)
-has been approved as an OASIS standard.
-
-Although, there are not many usages of SARIF as of writing (2020 July, 21),
-it can be good standard format.
-A promising usage example is [GitHub Code Scanning](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/about-code-scanning#about-third-party-code-scanning-tools)
-(beta), which uses SARIF to support third party code scanning tools.
-Other examples: [spotbugs](https://github.com/spotbugs/discuss/issues/95).
-
-Problems:
-- No stream output support and static analysis tools cannot output each diagnostic result one by one.
-- `columnKind` doesn't support byte count. https://github.com/oasis-tcs/sarif-spec/issues/466
-- The spec is too big and complex ([SARIF v2.1.0 PDF](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.pdf) is 227 pages!)
-  for developer tools as consumer of SARIF (e.g.  reviewdog). Probably most
-  tools end up with supporting SARIF partially.  GitHub Code Scanning feature
-  actually doesn't support a whole spec
-  ([doc](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/sarif-support-for-code-scanning))
-  for example.
-- The spec is too big and complex for static analysis tools as provider of
-  SARIF. They can just support partial and minimum SARIF support as result
-  output format but it's still not simple and the output still needs to pass
-  SARIF validatiotor.
-- Not all languages have good tools to generate code from JSON Schema.
-  To create Go SARIF package [haya14busa/go-sarif](https://github.com/haya14busa/go-sarif),
-  I needed to try 3+ [Go](https://github.com/atombender/go-jsonschema) [JSON Schema](https://github.com/idubinskiy/schematyper)
-  [Code Generator](https://github.com/aaharu/schemarshal)
-  tools but all of them didn't work for the complex SARIF JSON Schema.
-  I ended up using [quicktype](https://github.com/quicktype/quicktype) and it
-  worked but I still needed to send [a Pull Request](https://github.com/quicktype/quicktype/pull/1513)...
-- SARIF SDK and related tools are written in C# (and TypeScript), which means we need dotnet runtime.
-  SARIF is general and standard format while the related tools requires dotnet runtime.
-
-There are some problems as above but SARIF should be still good to support
-considering it has been already approved as an OASIS standard and GitHub Code
-Scanning uses it.
-Reviewdog Diagnostic Format can be used as simpler format and we can create
-converters between RD Format and SARIF.
-
 ### *Problem Matcher*
 [VSCode](https://vscode-docs.readthedocs.io/en/stable/editor/tasks/#defining-a-problem-matcher)
 and [GitHub Actions](https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md)
@@ -304,8 +262,17 @@ depend on the CLI, so providing libraries for their implementation languages
 should be useful to format results natively by each diagnostic tool.
 
 ## Open Questions
+- Naming:
+  - Is "Reviewdog Diagnostic Protocol" good naming?
+  - Should we really include *reviewdog* in the name? It's true that the
+    primary motivation is for the reviewdog CLI, but it can be just a standard
+    diagnostic format and it can be used outside reviwedog.
+  - Is "Protocol" good word? Should we use scheme/format/specification/etc...
+    instead?
+  - RDP is not unique acronym and [Remote Desktop Protocol](https://en.wikipedia.org/wiki/Remote_Desktop_Protocol)
+    is already using it.
 - Protocol Version Representation and Backward/Future Compatibility
-  - Should we add version or some capability data in RD Format?
-  - RD Format should be stable, but there are still a possibility to extend it with
+  - Should we add version or some capability data in RDP?
+  - RPD should be stable, but there are still a possibility to extend it with
     backward incompatible way. e.g. We **may** want to add byte offset field in
     Position message as an alternative of line and column.
diff --git a/reviewdog.go b/reviewdog.go
index c26711eb..dfc6efa9 100644
--- a/reviewdog.go
+++ b/reviewdog.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/reviewdog/reviewdog/diff"
 	"github.com/reviewdog/reviewdog/difffilter"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 // Reviewdog represents review dog application which parses result of compiler
@@ -37,11 +38,8 @@ func RunFromResult(ctx context.Context, c CommentService, results []*CheckResult
 // CheckResult represents a checked result of static analysis tools.
 // :h error-file-format
 type CheckResult struct {
-	Path    string   // relative file path
-	Lnum    int      // line number
-	Col     int      // column number (1 <tab> == 1 character column)
-	Message string   // error message
-	Lines   []string // Original error lines (often one line)
+	Diagnostic *rdf.Diagnostic
+	Lines      []string // Original error lines (often one line). (Optional)
 }
 
 // Parser is an interface which parses compilers, linters, or any tools
@@ -91,7 +89,7 @@ func (w *Reviewdog) runFromResult(ctx context.Context, results []*CheckResult,
 		}
 		comment := &Comment{
 			Result:   check,
-			Body:     check.Message,
+			Body:     check.Diagnostic.GetMessage(),
 			ToolName: w.toolname,
 		}
 		if err := w.c.Post(ctx, comment); err != nil {
diff --git a/service/commentutil/commentutil.go b/service/commentutil/commentutil.go
index 6d6a0446..1b78e8d0 100644
--- a/service/commentutil/commentutil.go
+++ b/service/commentutil/commentutil.go
@@ -14,10 +14,11 @@ type PostedComments map[string]map[int][]string
 // otherwise returns false. It sees comments with same path, same position,
 // and same body as same comments.
 func (p PostedComments) IsPosted(c *reviewdog.Comment, lineNum int) bool {
-	if _, ok := p[c.Result.Path]; !ok {
+	path := c.Result.Diagnostic.GetLocation().GetPath()
+	if _, ok := p[path]; !ok {
 		return false
 	}
-	bodies, ok := p[c.Result.Path][lineNum]
+	bodies, ok := p[path][lineNum]
 	if !ok {
 		return false
 	}
diff --git a/service/gerrit/change_review.go b/service/gerrit/change_review.go
index 7fd1c1d6..28561d07 100644
--- a/service/gerrit/change_review.go
+++ b/service/gerrit/change_review.go
@@ -48,7 +48,7 @@ func NewChangeReviewCommenter(cli *gerrit.Client, changeID, revisionID string) (
 
 // Post accepts a comment and holds it. Flush method actually posts comments to Gerrit
 func (g *ChangeReviewCommenter) Post(_ context.Context, c *reviewdog.Comment) error {
-	c.Result.Path = filepath.Join(g.wd, c.Result.Path)
+	c.Result.Diagnostic.GetLocation().Path = filepath.Join(g.wd, c.Result.Diagnostic.GetLocation().GetPath())
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -71,8 +71,10 @@ func (g *ChangeReviewCommenter) postAllComments(ctx context.Context) error {
 		if !c.Result.InDiffFile {
 			continue
 		}
-		review.Comments[c.Result.Path] = append(review.Comments[c.Result.Path], gerrit.CommentInput{
-			Line:    c.Result.Lnum,
+		loc := c.Result.Diagnostic.GetLocation()
+		path := loc.GetPath()
+		review.Comments[path] = append(review.Comments[path], gerrit.CommentInput{
+			Line:    int(loc.GetRange().GetStart().GetLine()),
 			Message: c.Body,
 		})
 	}
diff --git a/service/github/github.go b/service/github/github.go
index 246f6e35..8eb1d64f 100644
--- a/service/github/github.go
+++ b/service/github/github.go
@@ -7,7 +7,7 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 
 	"github.com/reviewdog/reviewdog"
 	"github.com/reviewdog/reviewdog/cienv"
@@ -62,7 +62,8 @@ func NewGitHubPullRequest(cli *github.Client, owner, repo string, pr int, sha st
 // Post accepts a comment and holds it. Flush method actually posts comments to
 // GitHub in parallel.
 func (g *GitHubPullRequest) Post(_ context.Context, c *reviewdog.Comment) error {
-	c.Result.Path = filepath.ToSlash(filepath.Join(g.wd, c.Result.Path))
+	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(filepath.Join(g.wd,
+		c.Result.Diagnostic.GetLocation().GetPath()))
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -109,9 +110,9 @@ func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error {
 		}
 		cbody := commentutil.CommentBody(c)
 		comments = append(comments, &github.DraftReviewComment{
-			Path:     &c.Result.Path,
-			Position: &c.Result.LnumDiff,
-			Body:     &cbody,
+			Path:     github.String(c.Result.Diagnostic.GetLocation().GetPath()),
+			Position: github.Int(c.Result.LnumDiff),
+			Body:     github.String(cbody),
 		})
 	}
 
diff --git a/service/github/githubutils/comment_writer.go b/service/github/githubutils/comment_writer.go
index 03e3bc25..0acf2645 100644
--- a/service/github/githubutils/comment_writer.go
+++ b/service/github/githubutils/comment_writer.go
@@ -49,11 +49,13 @@ func (lw *GitHubActionLogWriter) Flush(_ context.Context) error {
 // https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#example-5
 func ReportAsGitHubActionsLog(toolName, level string, c *reviewdog.CheckResult) {
 	mes := fmt.Sprintf("[%s] reported by reviewdog 🐶\n%s\n\nRaw Output:\n%s",
-		toolName, c.Message, strings.Join(c.Lines, "\n"))
+		toolName, c.Diagnostic.GetMessage(), strings.Join(c.Lines, "\n"))
+	loc := c.Diagnostic.GetLocation()
+	start := loc.GetRange().GetStart()
 	opt := &core.LogOption{
-		File: c.Path,
-		Line: c.Lnum,
-		Col:  c.Col,
+		File: c.Diagnostic.GetLocation().GetPath(),
+		Line: int(start.GetLine()),
+		Col:  int(start.GetColumn()),
 	}
 
 	switch level {
diff --git a/service/github/githubutils/utils.go b/service/github/githubutils/utils.go
index c968372d..44549eeb 100644
--- a/service/github/githubutils/utils.go
+++ b/service/github/githubutils/utils.go
@@ -9,12 +9,15 @@ import (
 // LinkedMarkdownCheckResult returns Markdown string which contains a link to the
 // location in the CheckResult and CheckResult content itself.
 func LinkedMarkdownCheckResult(owner, repo, sha string, c *reviewdog.CheckResult) string {
-	if c.Path == "" {
-		return c.Message
+	path := c.Diagnostic.GetLocation().GetPath()
+	msg := c.Diagnostic.GetMessage()
+	if path == "" {
+		return msg
 	}
 	loc := BasicLocationFormat(c)
-	link := PathLink(owner, repo, sha, c.Path, c.Lnum)
-	return fmt.Sprintf("[%s](%s) %s", loc, link, c.Message)
+	line := int(c.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
+	link := PathLink(owner, repo, sha, path, line)
+	return fmt.Sprintf("[%s](%s) %s", loc, link, msg)
 }
 
 // PathLink build a link to GitHub path to given sha, file, and line.
@@ -32,12 +35,15 @@ func PathLink(owner, repo, sha, path string, line int) string {
 
 // BasicLocationFormat format check CheckResult to %f|%l col %c| errorformat.
 func BasicLocationFormat(c *reviewdog.CheckResult) string {
-	loc := c.Path + "|"
-	if c.Lnum != 0 {
-		loc = fmt.Sprintf("%s%d", loc, c.Lnum)
-		if c.Col != 0 {
-			loc = fmt.Sprintf("%s col %d", loc, c.Col)
+	loc := c.Diagnostic.GetLocation()
+	out := loc.GetPath() + "|"
+	lnum := int(loc.GetRange().GetStart().GetLine())
+	col := int(loc.GetRange().GetStart().GetColumn())
+	if lnum != 0 {
+		out = fmt.Sprintf("%s%d", out, lnum)
+		if col != 0 {
+			out = fmt.Sprintf("%s col %d", out, col)
 		}
 	}
-	return loc + "|"
+	return out + "|"
 }
diff --git a/service/gitlab/gitlab_mr_commit.go b/service/gitlab/gitlab_mr_commit.go
index 4175c4c6..5ac443c3 100644
--- a/service/gitlab/gitlab_mr_commit.go
+++ b/service/gitlab/gitlab_mr_commit.go
@@ -57,7 +57,8 @@ func NewGitLabMergeRequestCommitCommenter(cli *gitlab.Client, owner, repo string
 // Post accepts a comment and holds it. Flush method actually posts comments to
 // GitLab in parallel.
 func (g *GitLabMergeRequestCommitCommenter) Post(_ context.Context, c *reviewdog.Comment) error {
-	c.Result.Path = filepath.ToSlash(filepath.Join(g.wd, c.Result.Path))
+	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
+		filepath.Join(g.wd, c.Result.Diagnostic.GetLocation().GetPath()))
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -80,21 +81,22 @@ func (g *GitLabMergeRequestCommitCommenter) postCommentsForEach(ctx context.Cont
 	var eg errgroup.Group
 	for _, c := range g.postComments {
 		c := c
-		if !c.Result.InDiffFile || c.Result.Lnum == 0 || g.postedcs.IsPosted(c, c.Result.Lnum) {
+		loc := c.Result.Diagnostic.GetLocation()
+		lnum := int(loc.GetRange().GetStart().GetLine())
+		if !c.Result.InDiffFile || lnum == 0 || g.postedcs.IsPosted(c, lnum) {
 			continue
 		}
 		eg.Go(func() error {
-			commitID, err := g.getLastCommitsID(c.Result.Path, c.Result.Lnum)
+			commitID, err := g.getLastCommitsID(loc.GetPath(), lnum)
 			if err != nil {
 				commitID = g.sha
 			}
 			body := commentutil.CommentBody(c)
-			ltype := "new"
 			prcomment := &gitlab.PostCommitCommentOptions{
-				Note:     &body,
-				Path:     &c.Result.Path,
-				Line:     &c.Result.Lnum,
-				LineType: &ltype,
+				Note:     gitlab.String(body),
+				Path:     gitlab.String(loc.GetPath()),
+				Line:     gitlab.Int(lnum),
+				LineType: gitlab.String("new"),
 			}
 			_, _, err = g.cli.Commits.PostCommitComment(g.projects, commitID, prcomment, gitlab.WithContext(ctx))
 			return err
diff --git a/service/gitlab/gitlab_mr_discussion.go b/service/gitlab/gitlab_mr_discussion.go
index 97bbcedd..510dbed3 100644
--- a/service/gitlab/gitlab_mr_discussion.go
+++ b/service/gitlab/gitlab_mr_discussion.go
@@ -51,7 +51,8 @@ func NewGitLabMergeRequestDiscussionCommenter(cli *gitlab.Client, owner, repo st
 // Post accepts a comment and holds it. Flush method actually posts comments to
 // GitLab in parallel.
 func (g *GitLabMergeRequestDiscussionCommenter) Post(_ context.Context, c *reviewdog.Comment) error {
-	c.Result.Path = filepath.Join(g.wd, c.Result.Path)
+	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
+		filepath.Join(g.wd, c.Result.Diagnostic.GetLocation().GetPath()))
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -100,7 +101,9 @@ func (g *GitLabMergeRequestDiscussionCommenter) postCommentsForEach(ctx context.
 	var eg errgroup.Group
 	for _, c := range g.postComments {
 		c := c
-		if !c.Result.InDiffFile || c.Result.Lnum == 0 || postedcs.IsPosted(c, c.Result.Lnum) {
+		loc := c.Result.Diagnostic.GetLocation()
+		lnum := int(loc.GetRange().GetStart().GetLine())
+		if !c.Result.InDiffFile || lnum == 0 || postedcs.IsPosted(c, lnum) {
 			continue
 		}
 		eg.Go(func() error {
@@ -109,8 +112,8 @@ func (g *GitLabMergeRequestDiscussionCommenter) postCommentsForEach(ctx context.
 				HeadSHA:      g.sha,
 				BaseSHA:      targetBranch.Commit.ID,
 				PositionType: "text",
-				NewPath:      c.Result.Path,
-				NewLine:      c.Result.Lnum,
+				NewPath:      loc.GetPath(),
+				NewLine:      lnum,
 			}
 			if c.Result.OldPath != "" && c.Result.OldLine != 0 {
 				pos.OldPath = c.Result.OldPath


Test patch:
diff --git a/cmd/reviewdog/doghouse_test.go b/cmd/reviewdog/doghouse_test.go
index 0c93bf0f..37acf666 100644
--- a/cmd/reviewdog/doghouse_test.go
+++ b/cmd/reviewdog/doghouse_test.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/oauth2"
+	"google.golang.org/protobuf/testing/protocmp"
 
 	"github.com/reviewdog/reviewdog"
 	"github.com/reviewdog/reviewdog/cienv"
@@ -19,6 +20,7 @@ import (
 	"github.com/reviewdog/reviewdog/doghouse"
 	"github.com/reviewdog/reviewdog/doghouse/client"
 	"github.com/reviewdog/reviewdog/project"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 func setupEnvs(testEnvs map[string]string) (cleanup func()) {
@@ -121,10 +123,16 @@ func TestCheckResultSet_Project(t *testing.T) {
 	var wantCheckResult reviewdog.ResultMap
 	wantCheckResult.Store("name1", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
-			Lnum:    1,
-			Col:     14,
-			Message: "msg",
-			Path:    "reviewdog.go",
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line:   1,
+						Column: 14,
+					}},
+					Path: "reviewdog.go",
+				},
+				Message: "msg",
+			},
 		},
 	}})
 
@@ -148,7 +156,7 @@ func TestCheckResultSet_Project(t *testing.T) {
 	}
 	got.Range(func(k string, r *reviewdog.Result) {
 		w, _ := wantCheckResult.Load(k)
-		if diff := cmp.Diff(r, w); diff != "" {
+		if diff := cmp.Diff(r, w, protocmp.Transform()); diff != "" {
 			t.Errorf("result has diff:\n%s", diff)
 		}
 	})
@@ -166,11 +174,17 @@ func TestCheckResultSet_NonProject(t *testing.T) {
 	var want reviewdog.ResultMap
 	want.Store("golint", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
-			Lnum:    14,
-			Col:     14,
-			Message: "test message",
-			Path:    "reviewdog.go",
-			Lines:   []string{input},
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line:   14,
+						Column: 14,
+					}},
+					Path: "reviewdog.go",
+				},
+				Message: "test message",
+			},
+			Lines: []string{input},
 		},
 	}})
 
@@ -179,7 +193,7 @@ func TestCheckResultSet_NonProject(t *testing.T) {
 	}
 	got.Range(func(k string, r *reviewdog.Result) {
 		w, _ := want.Load(k)
-		if diff := cmp.Diff(r, w); diff != "" {
+		if diff := cmp.Diff(r, w, protocmp.Transform()); diff != "" {
 			t.Errorf("result has diff:\n%s", diff)
 		}
 	})
@@ -252,21 +266,37 @@ func TestPostResultSet_withReportURL(t *testing.T) {
 	var resultSet reviewdog.ResultMap
 	resultSet.Store("name1", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
-			Lnum:    14,
-			Message: "name1: test 1",
-			Path:    "reviewdog.go", // test relative path
-			Lines:   []string{"L1", "L2"},
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 14,
+					}},
+					Path: "reviewdog.go", // test relative path
+				},
+				Message: "name1: test 1",
+			},
+			Lines: []string{"L1", "L2"},
 		},
 		{
-			Message: "name1: test 2",
-			Path:    absPath(t, "reviewdog.go"), // test abs path
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: absPath(t, "reviewdog.go"), // test abs path
+				},
+				Message: "name1: test 2",
+			},
 		},
 	}})
 	resultSet.Store("name2", &reviewdog.Result{CheckResults: []*reviewdog.CheckResult{
 		{
-			Lnum:    14,
-			Message: "name2: test 1",
-			Path:    "doghouse.go",
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 14,
+					}},
+					Path: "doghouse.go",
+				},
+				Message: "name2: test 1",
+			},
 		},
 	}})
 
@@ -314,7 +344,7 @@ func TestPostResultSet_withoutReportURL(t *testing.T) {
 	if err != nil {
 		t.Fatalf("should have result for name1: %v", err)
 	}
-	if diff := cmp.Diff(results.FilteredCheck, wantResults); diff != "" {
+	if diff := cmp.Diff(results.FilteredCheck, wantResults, protocmp.Transform()); diff != "" {
 		t.Errorf("results has diff:\n%s", diff)
 	}
 }
diff --git a/comment_iowriter_test.go b/comment_iowriter_test.go
index 5084caf4..b6ab0438 100644
--- a/comment_iowriter_test.go
+++ b/comment_iowriter_test.go
@@ -5,6 +5,8 @@ import (
 	"context"
 	"strings"
 	"testing"
+
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 func TestUnifiedCommentWriter_Post(t *testing.T) {
@@ -15,7 +17,7 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		{
 			in: &Comment{
 				Result: &FilteredCheck{CheckResult: &CheckResult{
-					Path: "/path/to/file",
+					Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: "/path/to/file"}},
 				}},
 				ToolName: "tool name",
 				Body:     "message",
@@ -25,8 +27,12 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		{
 			in: &Comment{
 				Result: &FilteredCheck{CheckResult: &CheckResult{
-					Path: "/path/to/file",
-					Col:  14,
+					Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{
+						Path: "/path/to/file",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Column: 14,
+						}},
+					}},
 				}},
 				ToolName: "tool name",
 				Body:     "message",
@@ -36,8 +42,12 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		{
 			in: &Comment{
 				Result: &FilteredCheck{CheckResult: &CheckResult{
-					Path: "/path/to/file",
-					Lnum: 14,
+					Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{
+						Path: "/path/to/file",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line: 14,
+						}},
+					}},
 				}},
 				ToolName: "tool name",
 				Body:     "message",
@@ -47,9 +57,13 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		{
 			in: &Comment{
 				Result: &FilteredCheck{CheckResult: &CheckResult{
-					Path: "/path/to/file",
-					Lnum: 14,
-					Col:  7,
+					Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{
+						Path: "/path/to/file",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line:   14,
+							Column: 7,
+						}},
+					}},
 				}},
 				ToolName: "tool name",
 				Body:     "line1\nline2",
diff --git a/doghouse/server/doghouse_test.go b/doghouse/server/doghouse_test.go
index d28fd4ed..0a4bbf32 100644
--- a/doghouse/server/doghouse_test.go
+++ b/doghouse/server/doghouse_test.go
@@ -7,7 +7,7 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 
 	"github.com/reviewdog/reviewdog/difffilter"
 	"github.com/reviewdog/reviewdog/doghouse"
diff --git a/filter_test.go b/filter_test.go
index a6934396..346aa565 100644
--- a/filter_test.go
+++ b/filter_test.go
@@ -5,9 +5,11 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/testing/protocmp"
 
 	"github.com/reviewdog/reviewdog/diff"
 	"github.com/reviewdog/reviewdog/difffilter"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 const diffContent = `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
@@ -45,27 +47,47 @@ index 0000000..264c67e
 func TestFilterCheckByAddedLines(t *testing.T) {
 	results := []*CheckResult{
 		{
-			Path: "sample.new.txt",
-			Lnum: 1,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "sample.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 1}},
+				},
+			},
 		},
 		{
-			Path: "sample.new.txt",
-			Lnum: 2,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "sample.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 2}},
+				},
+			},
 		},
 		{
-			Path: "nonewline.new.txt",
-			Lnum: 1,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "nonewline.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 1}},
+				},
+			},
 		},
 		{
-			Path: "nonewline.new.txt",
-			Lnum: 3,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "nonewline.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 3}},
+				},
+			},
 		},
 	}
 	want := []*FilteredCheck{
 		{
 			CheckResult: &CheckResult{
-				Path: "sample.new.txt",
-				Lnum: 1,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "sample.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 1}},
+					},
+				},
 			},
 			ShouldReport: false,
 			InDiffFile:   true,
@@ -74,8 +96,12 @@ func TestFilterCheckByAddedLines(t *testing.T) {
 		},
 		{
 			CheckResult: &CheckResult{
-				Path: "sample.new.txt",
-				Lnum: 2,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "sample.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 2}},
+					},
+				},
 			},
 			ShouldReport: true,
 			InDiffFile:   true,
@@ -85,8 +111,12 @@ func TestFilterCheckByAddedLines(t *testing.T) {
 		},
 		{
 			CheckResult: &CheckResult{
-				Path: "nonewline.new.txt",
-				Lnum: 1,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "nonewline.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 1}},
+					},
+				},
 			},
 			ShouldReport: false,
 			InDiffFile:   true,
@@ -95,8 +125,12 @@ func TestFilterCheckByAddedLines(t *testing.T) {
 		},
 		{
 			CheckResult: &CheckResult{
-				Path: "nonewline.new.txt",
-				Lnum: 3,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "nonewline.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 3}},
+					},
+				},
 			},
 			ShouldReport: true,
 			InDiffFile:   true,
@@ -107,7 +141,7 @@ func TestFilterCheckByAddedLines(t *testing.T) {
 	}
 	filediffs, _ := diff.ParseMultiFile(strings.NewReader(diffContent))
 	got := FilterCheck(results, filediffs, 0, "", difffilter.ModeAdded)
-	if diff := cmp.Diff(got, want); diff != "" {
+	if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" {
 		t.Error(diff)
 	}
 }
@@ -116,23 +150,39 @@ func TestFilterCheckByAddedLines(t *testing.T) {
 func TestFilterCheckByDiffContext(t *testing.T) {
 	results := []*CheckResult{
 		{
-			Path: "sample.new.txt",
-			Lnum: 1,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "sample.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 1}},
+				},
+			},
 		},
 		{
-			Path: "sample.new.txt",
-			Lnum: 2,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "sample.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 2}},
+				},
+			},
 		},
 		{
-			Path: "sample.new.txt",
-			Lnum: 3,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path:  "sample.new.txt",
+					Range: &rdf.Range{Start: &rdf.Position{Line: 3}},
+				},
+			},
 		},
 	}
 	want := []*FilteredCheck{
 		{
 			CheckResult: &CheckResult{
-				Path: "sample.new.txt",
-				Lnum: 1,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "sample.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 1}},
+					},
+				},
 			},
 			ShouldReport: true,
 			InDiffFile:   true,
@@ -142,8 +192,12 @@ func TestFilterCheckByDiffContext(t *testing.T) {
 		},
 		{
 			CheckResult: &CheckResult{
-				Path: "sample.new.txt",
-				Lnum: 2,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "sample.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 2}},
+					},
+				},
 			},
 			ShouldReport: true,
 			InDiffFile:   true,
@@ -153,8 +207,12 @@ func TestFilterCheckByDiffContext(t *testing.T) {
 		},
 		{
 			CheckResult: &CheckResult{
-				Path: "sample.new.txt",
-				Lnum: 3,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path:  "sample.new.txt",
+						Range: &rdf.Range{Start: &rdf.Position{Line: 3}},
+					},
+				},
 			},
 			ShouldReport: true,
 			InDiffFile:   true,
@@ -165,7 +223,7 @@ func TestFilterCheckByDiffContext(t *testing.T) {
 	}
 	filediffs, _ := diff.ParseMultiFile(strings.NewReader(diffContent))
 	got := FilterCheck(results, filediffs, 0, "", difffilter.ModeDiffContext)
-	if diff := cmp.Diff(got, want); diff != "" {
+	if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" {
 		t.Error(diff)
 	}
 }
diff --git a/reviewdog_test.go b/reviewdog_test.go
index 4f383cd4..92e0f874 100644
--- a/reviewdog_test.go
+++ b/reviewdog_test.go
@@ -84,7 +84,7 @@ index 34cacb9..a727dd3 100644
 
 	c := &testWriter{
 		FakePost: func(c *Comment) error {
-			if got := c.Result.Path; got != want {
+			if got := c.Result.Diagnostic.GetLocation().GetPath(); got != want {
 				t.Errorf("path: got %v, want %v", got, want)
 			}
 			return nil
diff --git a/service/gerrit/change_review_test.go b/service/gerrit/change_review_test.go
index 1bb1f80a..05d7b542 100644
--- a/service/gerrit/change_review_test.go
+++ b/service/gerrit/change_review_test.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/reviewdog/reviewdog"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 	"golang.org/x/build/gerrit"
 )
 
@@ -29,23 +30,41 @@ func TestChangeReviewCommenter_Post_Flush(t *testing.T) {
 	newLnum1 := 14
 	newComment1 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "file.go",
-			Lnum: newLnum1,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "file.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: int32(newLnum1),
+					}},
+				},
+			},
 		}, InDiffFile: true},
 		Body: "new comment",
 	}
 	newLnum2 := 15
 	newComment2 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "file2.go",
-			Lnum: newLnum2,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "file2.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: int32(newLnum2),
+					}},
+				},
+			},
 		}, InDiffFile: true},
 		Body: "new comment 2",
 	}
 	commentOutsideDiff := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "file3.go",
-			Lnum: 14,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "file3.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 14,
+					}},
+				},
+			},
 		}, InDiffFile: false},
 		Body: "comment outside diff",
 	}
@@ -69,12 +88,14 @@ func TestChangeReviewCommenter_Post_Flush(t *testing.T) {
 				t.Errorf("got %d comments, want %d", len(got.Comments), want)
 			}
 
-			want := []gerrit.CommentInput{{Line: newComment1.Result.Lnum, Message: newComment1.Body}}
+			line1 := int(newComment1.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
+			want := []gerrit.CommentInput{{Line: line1, Message: newComment1.Body}}
 			if diff := cmp.Diff(got.Comments["file.go"], want); diff != "" {
 				t.Error(diff)
 			}
 
-			want = []gerrit.CommentInput{{Line: newComment2.Result.Lnum, Message: newComment2.Body}}
+			line2 := int(newComment2.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
+			want = []gerrit.CommentInput{{Line: line2, Message: newComment2.Body}}
 			if diff := cmp.Diff(got.Comments["file2.go"], want); diff != "" {
 				t.Error(diff)
 			}
diff --git a/service/github/github_test.go b/service/github/github_test.go
index 88b4bffb..73319c45 100644
--- a/service/github/github_test.go
+++ b/service/github/github_test.go
@@ -10,11 +10,12 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/google/go-github/v32/github"
+	"github.com/google/go-github/v31/github"
 	"github.com/kylelemons/godebug/pretty"
 	"golang.org/x/oauth2"
 
 	"github.com/reviewdog/reviewdog"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 	"github.com/reviewdog/reviewdog/service/commentutil"
 )
 
@@ -72,7 +73,11 @@ func TestGitHubPullRequest_Post(t *testing.T) {
 	comment := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{
 			CheckResult: &reviewdog.CheckResult{
-				Path: "watchdogs.go",
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "watchdogs.go",
+					},
+				},
 			},
 			LnumDiff: 17,
 		},
@@ -271,7 +276,11 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 		{
 			Result: &reviewdog.FilteredCheck{
 				CheckResult: &reviewdog.CheckResult{
-					Path: "reviewdog.go",
+					Diagnostic: &rdf.Diagnostic{
+						Location: &rdf.Location{
+							Path: "reviewdog.go",
+						},
+					},
 				},
 				LnumDiff: 1,
 			},
@@ -280,7 +289,11 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 		{
 			Result: &reviewdog.FilteredCheck{
 				CheckResult: &reviewdog.CheckResult{
-					Path: "reviewdog.go",
+					Diagnostic: &rdf.Diagnostic{
+						Location: &rdf.Location{
+							Path: "reviewdog.go",
+						},
+					},
 				},
 				LnumDiff: 14,
 			},
@@ -289,7 +302,11 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 		{
 			Result: &reviewdog.FilteredCheck{
 				CheckResult: &reviewdog.CheckResult{
-					Path: "reviewdog.go",
+					Diagnostic: &rdf.Diagnostic{
+						Location: &rdf.Location{
+							Path: "reviewdog.go",
+						},
+					},
 				},
 				LnumDiff: 14,
 			},
@@ -298,7 +315,11 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 		{
 			Result: &reviewdog.FilteredCheck{
 				CheckResult: &reviewdog.CheckResult{
-					Path: "reviewdog.go",
+					Diagnostic: &rdf.Diagnostic{
+						Location: &rdf.Location{
+							Path: "reviewdog.go",
+						},
+					},
 				},
 				// No LnumDiff.
 			},
@@ -361,8 +382,14 @@ func TestGitHubPullRequest_Post_toomany(t *testing.T) {
 		comments = append(comments, &reviewdog.Comment{
 			Result: &reviewdog.FilteredCheck{
 				CheckResult: &reviewdog.CheckResult{
-					Path: "reviewdog.go",
-					Lnum: i,
+					Diagnostic: &rdf.Diagnostic{
+						Location: &rdf.Location{
+							Path: "reviewdog.go",
+							Range: &rdf.Range{Start: &rdf.Position{
+								Line: int32(i),
+							}},
+						},
+					},
 				},
 				LnumDiff: i,
 			},
@@ -401,8 +428,9 @@ func TestGitHubPullRequest_workdir(t *testing.T) {
 	}
 	ctx := context.Background()
 	want := "a/b/c"
-	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: want}}})
-	if got := g.postComments[0].Result.Path; got != want {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+		Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: want}}}}})
+	if got := g.postComments[0].Result.Diagnostic.GetLocation().GetPath(); got != want {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, want)
 	}
 
@@ -416,8 +444,9 @@ func TestGitHubPullRequest_workdir(t *testing.T) {
 	}
 	path := "a/b/c"
 	wantPath := "cmd/" + path
-	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: path}}})
-	if got := g.postComments[0].Result.Path; got != wantPath {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+		Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: want}}}}})
+	if got := g.postComments[0].Result.Diagnostic.GetLocation().GetPath(); got != wantPath {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, wantPath)
 	}
 }
diff --git a/service/github/githubutils/utils_test.go b/service/github/githubutils/utils_test.go
index ffc6612c..9ea778fa 100644
--- a/service/github/githubutils/utils_test.go
+++ b/service/github/githubutils/utils_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/reviewdog/reviewdog"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 )
 
 func TestLinkedMarkdownCheckResult(t *testing.T) {
@@ -17,10 +18,16 @@ func TestLinkedMarkdownCheckResult(t *testing.T) {
 			repo:  "r",
 			sha:   "s",
 			c: &reviewdog.CheckResult{
-				Path:    "path/to/file.txt",
-				Lnum:    1414,
-				Col:     14,
-				Message: "msg",
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "path/to/file.txt",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line:   1414,
+							Column: 14,
+						}},
+					},
+					Message: "msg",
+				},
 			},
 			want: "[path/to/file.txt|1414 col 14|](http://github.com/o/r/blob/s/path/to/file.txt#L1414) msg",
 		},
@@ -29,10 +36,16 @@ func TestLinkedMarkdownCheckResult(t *testing.T) {
 			repo:  "r",
 			sha:   "s",
 			c: &reviewdog.CheckResult{
-				Path:    "path/to/file.txt",
-				Lnum:    1414,
-				Col:     0,
-				Message: "msg",
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "path/to/file.txt",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line:   1414,
+							Column: 0,
+						}},
+					},
+					Message: "msg",
+				},
 			},
 			want: "[path/to/file.txt|1414|](http://github.com/o/r/blob/s/path/to/file.txt#L1414) msg",
 		},
@@ -41,10 +54,12 @@ func TestLinkedMarkdownCheckResult(t *testing.T) {
 			repo:  "r",
 			sha:   "s",
 			c: &reviewdog.CheckResult{
-				Path:    "path/to/file.txt",
-				Lnum:    0,
-				Col:     0,
-				Message: "msg",
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "path/to/file.txt",
+					},
+					Message: "msg",
+				},
 			},
 			want: "[path/to/file.txt||](http://github.com/o/r/blob/s/path/to/file.txt) msg",
 		},
diff --git a/service/gitlab/gitlab_mr_commit_test.go b/service/gitlab/gitlab_mr_commit_test.go
index a64c179d..20a4d11a 100644
--- a/service/gitlab/gitlab_mr_commit_test.go
+++ b/service/gitlab/gitlab_mr_commit_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/xanzy/go-gitlab"
 
 	"github.com/reviewdog/reviewdog"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 	"github.com/reviewdog/reviewdog/service/commentutil"
 )
 
@@ -92,15 +93,27 @@ func TestGitLabMergeRequestCommitCommenter_Post_Flush_review_api(t *testing.T) {
 	comments := []*reviewdog.Comment{
 		{
 			Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-				Path: "notExistFile.go",
-				Lnum: 1,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "notExistFile.go",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line: 1,
+						}},
+					},
+				},
 			}, InDiffFile: true},
 			Body: "already commented",
 		},
 		{
 			Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-				Path: "notExistFile.go",
-				Lnum: 14,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "notExistFile.go",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line: 14,
+						}},
+					},
+				},
 			}, InDiffFile: true},
 			Body: "new comment",
 		},
@@ -132,8 +145,9 @@ func TestGitLabPullRequest_workdir(t *testing.T) {
 	}
 	ctx := context.Background()
 	want := "a/b/c"
-	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: want}}})
-	if got := g.postComments[0].Result.Path; got != want {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+		Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: want}}}}})
+	if got := g.postComments[0].Result.Diagnostic.GetLocation().GetPath(); got != want {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, want)
 	}
 
@@ -147,8 +161,9 @@ func TestGitLabPullRequest_workdir(t *testing.T) {
 	}
 	path := "a/b/c"
 	wantPath := "cmd/" + path
-	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: path}}})
-	if got := g.postComments[0].Result.Path; got != wantPath {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+		Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: want}}}}})
+	if got := g.postComments[0].Result.Diagnostic.GetLocation().GetPath(); got != wantPath {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, wantPath)
 	}
 }
diff --git a/service/gitlab/gitlab_mr_discussion_test.go b/service/gitlab/gitlab_mr_discussion_test.go
index 40751de9..cbbd24ce 100644
--- a/service/gitlab/gitlab_mr_discussion_test.go
+++ b/service/gitlab/gitlab_mr_discussion_test.go
@@ -13,6 +13,7 @@ import (
 	"github.com/xanzy/go-gitlab"
 
 	"github.com/reviewdog/reviewdog"
+	"github.com/reviewdog/reviewdog/proto/rdf"
 	"github.com/reviewdog/reviewdog/service/commentutil"
 )
 
@@ -23,37 +24,67 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 
 	alreadyCommented1 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "file.go",
-			Lnum: 1,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "file.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 1,
+					}},
+				},
+			},
 		}, InDiffFile: true},
 		Body: "already commented",
 	}
 	alreadyCommented2 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "another/file.go",
-			Lnum: 14,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "another/file.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 14,
+					}},
+				},
+			},
 		}, InDiffFile: true},
 		Body: "already commented 2",
 	}
 	newComment1 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "file.go",
-			Lnum: 14,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "file.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 14,
+					}},
+				},
+			},
 		}, InDiffFile: true},
 		Body: "new comment",
 	}
 	newComment2 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "file2.go",
-			Lnum: 15,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "file2.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 15,
+					}},
+				},
+			},
 		}, InDiffFile: true},
 		Body: "new comment 2",
 	}
 	newComment3 := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{
 			CheckResult: &reviewdog.CheckResult{
-				Path: "new_file.go",
-				Lnum: 14,
+				Diagnostic: &rdf.Diagnostic{
+					Location: &rdf.Location{
+						Path: "new_file.go",
+						Range: &rdf.Range{Start: &rdf.Position{
+							Line: 14,
+						}},
+					},
+				},
 			},
 			OldPath:    "old_file.go",
 			OldLine:    7,
@@ -63,15 +94,24 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 	}
 	commentOutsideDiff := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "path.go",
-			Lnum: 14,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "path.go",
+					Range: &rdf.Range{Start: &rdf.Position{
+						Line: 14,
+					}},
+				},
+			},
 		}, InDiffFile: false},
 		Body: "comment outside diff",
 	}
 	commentWithoutLnum := &reviewdog.Comment{
 		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
-			Path: "path.go",
-			Lnum: 0,
+			Diagnostic: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "path.go",
+				},
+			},
 		}, InDiffFile: true},
 		Body: "comment without lnum",
 	}
@@ -100,8 +140,8 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 							{
 								Body: commentutil.CommentBody(alreadyCommented1),
 								Position: &gitlab.NotePosition{
-									NewPath: alreadyCommented1.Result.Path,
-									NewLine: alreadyCommented1.Result.Lnum,
+									NewPath: alreadyCommented1.Result.Diagnostic.GetLocation().GetPath(),
+									NewLine: int(alreadyCommented1.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine()),
 								},
 							},
 							{
@@ -125,8 +165,8 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 							{
 								Body: commentutil.CommentBody(alreadyCommented2),
 								Position: &gitlab.NotePosition{
-									NewPath: alreadyCommented2.Result.Path,
-									NewLine: alreadyCommented2.Result.Lnum,
+									NewPath: alreadyCommented2.Result.Diagnostic.GetLocation().GetPath(),
+									NewLine: int(alreadyCommented2.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine()),
 								},
 							},
 						},