Repository: reviewdog/reviewdog
Pull request #541 (2020-05-04 17:19:05)

Implement -filter-mode=nofilter
nofilter mode won't filter out any results (but attach necessary diff data
if available).

It should be useful for ...
- Check all results to console and report some results as comment to GitHub
where possible. Can be used with -fail-on-error as well.
- Debug

related: #536, #476


Patch:
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
index e3baac09..39fdae32 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/reviewdog.yml
@@ -107,7 +107,7 @@ jobs:
           echo 'var LocalTest = 14' >> ./_testdata/golint.go
           cd ./_testdata/ && golint ./... | reviewdog -f=golint -diff="git diff" -fail-on-error || EXIT_CODE=$?
           git reset --hard @
-          test ${EXIT_CODE} = 1
+          test "${EXIT_CODE}" = 1
 
       - name: Run reviewdog (github-pr-check with fail-on-error)
         continue-on-error: true
@@ -122,6 +122,13 @@ jobs:
         run: |
           golint | reviewdog -f=golint -name=golint-pr-check-filter-mode-file -reporter=github-pr-check -filter-mode=file -level=warning
 
+      - name: Run reviewdog (github-pr-review -filter-mode=nofilter)
+        env:
+          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          cd ./_testdata/ && golint ./... | reviewdog -f=golint -name=golint-pr-review-nofilter -reporter=github-pr-review -filter-mode=nofilter -fail-on-error || EXIT_CODE=$?
+          test "${EXIT_CODE}" = 1
+
   golangci-lint:
     if: github.event_name == 'pull_request'
     name: runner / golangci-lint
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dc485724..20eaef9a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -40,6 +40,6 @@ Reviewdog:
     - go install ./cmd/reviewdog
     - ( cd linters && go get golang.org/x/lint/golint )
   script:
-    - golint ./... | reviewdog -f=golint -name=golint-discussion -reporter=gitlab-mr-discussion -filter-mode=file
+    - golint ./... | reviewdog -f=golint -name=golint-discussion -reporter=gitlab-mr-discussion -filter-mode=nofilter
     - golint ./... | reviewdog -f=golint -name=golint-commit -reporter=gitlab-mr-commit
     - cd ./_testdata/ && golint ./... | reviewdog -f=golint -name=golint-discussion-subdir -reporter=gitlab-mr-discussion
diff --git a/cmd/reviewdog/doghouse.go b/cmd/reviewdog/doghouse.go
index 95a88633..126321e4 100644
--- a/cmd/reviewdog/doghouse.go
+++ b/cmd/reviewdog/doghouse.go
@@ -45,7 +45,7 @@ func runDoghouse(ctx context.Context, r io.Reader, w io.Writer, opt *option, isP
 	if err != nil {
 		return err
 	}
-	if foundResultInDiff := reportResults(w, filteredResultSet); foundResultInDiff {
+	if foundResultShouldReport := reportResults(w, filteredResultSet); foundResultShouldReport {
 		return errors.New("found at least one result in diff")
 	}
 	return nil
@@ -220,7 +220,7 @@ report results via logging command [1].
 		foundResultPerName := false
 		filteredNum := 0
 		for _, result := range results.FilteredCheck {
-			if !result.InDiff {
+			if !result.ShouldReport {
 				filteredNum++
 				continue
 			}
diff --git a/cmd/reviewdog/main.go b/cmd/reviewdog/main.go
index 7c02d2d1..3c3e28f6 100644
--- a/cmd/reviewdog/main.go
+++ b/cmd/reviewdog/main.go
@@ -74,14 +74,16 @@ const (
 	levelDoc            = `report level currently used for github-pr-check reporter ("info","warning","error").`
 	guessPullRequestDoc = `guess Pull Request ID by branch name and commit SHA`
 	teeDoc              = `enable "tee"-like mode which outputs tools's output as is while reporting results to -reporter. Useful for debugging as well.`
-	filterModeDoc       = `how to filter checks results. [added, diff_context, file].
-    "added" (default)
-        Filter by added/modified diff lines.
-    "diff_context"
-        Filter by diff context, which can include unchanged lines.
-				i.e. changed lines +-N lines (e.g. N=3 for default git diff).
-    "file"
-        Filter by added/modified file.
+	filterModeDoc       = `how to filter checks results. [added, diff_context, file, nofilter].
+		"added" (default)
+			Filter by added/modified diff lines.
+		"diff_context"
+			Filter by diff context, which can include unchanged lines.
+			i.e. changed lines +-N lines (e.g. N=3 for default git diff).
+		"file"
+			Filter by added/modified file.
+		"nofilter"
+			Do not filter any results.
 `
 	reporterDoc = `reporter of reviewdog results. (local, github-check, github-pr-check, github-pr-review, gitlab-mr-discussion, gitlab-mr-commit)
 	"local" (default)
diff --git a/comment_iowriter.go b/comment_iowriter.go
index 7e9897b8..c04fce01 100644
--- a/comment_iowriter.go
+++ b/comment_iowriter.go
@@ -20,7 +20,7 @@ func NewRawCommentWriter(w io.Writer) *RawCommentWriter {
 }
 
 func (s *RawCommentWriter) Post(_ context.Context, c *Comment) error {
-	_, err := fmt.Fprintln(s.w, strings.Join(c.CheckResult.Lines, "\n"))
+	_, err := fmt.Fprintln(s.w, strings.Join(c.Result.Lines, "\n"))
 	return err
 }
 
@@ -43,11 +43,11 @@ func NewUnifiedCommentWriter(w io.Writer) *UnifiedCommentWriter {
 }
 
 func (mc *UnifiedCommentWriter) Post(_ context.Context, c *Comment) error {
-	s := c.Path
-	if c.Lnum > 0 {
-		s += fmt.Sprintf(":%d", c.Lnum)
-		if c.Col > 0 {
-			s += fmt.Sprintf(":%d", c.Col)
+	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)
 		}
 	}
 	s += fmt.Sprintf(": [%s] %s", c.ToolName, c.Body)
diff --git a/difffilter/filter.go b/difffilter/filter.go
index 46a30efc..2bc4ee2e 100644
--- a/difffilter/filter.go
+++ b/difffilter/filter.go
@@ -25,6 +25,8 @@ const (
 	ModeDiffContext
 	// ModeFile represents filtering by changed files.
 	ModeFile
+	// ModeNoFilter doesn't filter out any results.
+	ModeNoFilter
 )
 
 // String implements the flag.Value interface
@@ -34,8 +36,9 @@ func (mode *Mode) String() string {
 		"added",
 		"diff_context",
 		"file",
+		"nofilter",
 	}
-	if *mode < ModeDefault || *mode > ModeFile {
+	if *mode < ModeDefault || *mode > ModeNoFilter {
 		return "Unknown mode"
 	}
 
@@ -53,6 +56,8 @@ func (mode *Mode) Set(value string) error {
 		*mode = ModeDiffContext
 	case "file":
 		*mode = ModeFile
+	case "nofilter":
+		*mode = ModeNoFilter
 	default:
 		return fmt.Errorf("invalid mode name: %s", value)
 	}
@@ -72,11 +77,15 @@ type DiffFilter struct {
 	mode  Mode
 
 	difflines difflines
+	difffiles difffiles
 }
 
-// difflines is a hash table of normalizedPath to line number to diff.Line.
+// difflines is a hash table of normalizedPath to line number to *diff.Line.
 type difflines map[normalizedPath]map[int]*diff.Line
 
+// difffiles is a hash table of normalizedPath to *diff.FileDiff.
+type difffiles map[normalizedPath]*diff.FileDiff
+
 // New creates a new DiffFilter.
 func New(diff []*diff.FileDiff, strip int, cwd string, mode Mode) *DiffFilter {
 	df := &DiffFilter{
@@ -84,6 +93,7 @@ func New(diff []*diff.FileDiff, strip int, cwd string, mode Mode) *DiffFilter {
 		cwd:       cwd,
 		mode:      mode,
 		difflines: make(difflines),
+		difffiles: make(difffiles),
 	}
 	// If cwd is empty, projectRelPath should not have any meaningful data too.
 	if cwd != "" {
@@ -96,13 +106,14 @@ func New(diff []*diff.FileDiff, strip int, cwd string, mode Mode) *DiffFilter {
 func (df *DiffFilter) addDiff(filediffs []*diff.FileDiff) {
 	for _, filediff := range filediffs {
 		path := df.normalizeDiffPath(filediff)
+		df.difffiles[path] = filediff
 		lines, ok := df.difflines[path]
 		if !ok {
 			lines = make(map[int]*diff.Line)
 		}
 		for _, hunk := range filediff.Hunks {
 			for _, line := range hunk.Lines {
-				if df.isSignificantLine(line) {
+				if line.LnumNew > 0 && df.isSignificantLine(line) {
 					lines[line.LnumNew] = line
 				}
 			}
@@ -111,26 +122,25 @@ func (df *DiffFilter) addDiff(filediffs []*diff.FileDiff) {
 	}
 }
 
-// InDiff returns true, if the given path is in diff depending on the filter
-// Mode. It also optionally return diff line.
-func (df *DiffFilter) InDiff(path string, lnum int) (bool, *diff.Line) {
-	lines, ok := df.difflines[df.normalizePath(path)]
+// ShouldReport returns true, if the given path should be reported depending on
+// the filter Mode. It also optionally return diff file/line.
+func (df *DiffFilter) ShouldReport(path string, lnum int) (bool, *diff.FileDiff, *diff.Line) {
+	npath := df.normalizePath(path)
+	file := df.difffiles[npath]
+	lines, ok := df.difflines[npath]
 	if !ok {
-		return false, nil
+		return (df.mode == ModeNoFilter), file, nil
 	}
 	line, ok := lines[lnum]
 	if !ok {
-		if df.mode == ModeFile {
-			return true, nil
-		}
-		return false, nil
+		return (df.mode == ModeNoFilter || df.mode == ModeFile), file, nil
 	}
-	return true, line
+	return true, file, line
 }
 
 func (df *DiffFilter) isSignificantLine(line *diff.Line) bool {
 	switch df.mode {
-	case ModeDiffContext, ModeFile:
+	case ModeDiffContext, ModeFile, ModeNoFilter:
 		return true // any lines in diff are significant.
 	case ModeAdded, ModeDefault:
 		return line.Type == diff.LineAdded
diff --git a/doghouse/server/doghouse.go b/doghouse/server/doghouse.go
index 486602ca..28b59671 100644
--- a/doghouse/server/doghouse.go
+++ b/doghouse/server/doghouse.go
@@ -79,7 +79,7 @@ func (ch *Checker) postCheck(ctx context.Context, checkID int64, checks []*revie
 	filterByDiff := ch.req.PullRequest != 0 && !ch.req.OutsideDiff
 	var annotations []*github.CheckRunAnnotation
 	for _, c := range checks {
-		if !c.InDiff && filterByDiff {
+		if !c.ShouldReport && filterByDiff {
 			continue
 		}
 		annotations = append(annotations, ch.toCheckRunAnnotation(c))
@@ -181,7 +181,7 @@ func (ch *Checker) summary(checks []*reviewdog.FilteredCheck) string {
 	var findings []*reviewdog.FilteredCheck
 	var filteredFindings []*reviewdog.FilteredCheck
 	for _, c := range checks {
-		if c.InDiff {
+		if c.ShouldReport {
 			findings = append(findings, c)
 		} else {
 			filteredFindings = append(filteredFindings, c)
diff --git a/filter.go b/filter.go
index 3bc76a01..9a645fe6 100644
--- a/filter.go
+++ b/filter.go
@@ -10,28 +10,31 @@ import (
 // FilteredCheck represents CheckResult with filtering info.
 type FilteredCheck struct {
 	*CheckResult
-	InDiff   bool
-	LnumDiff int
-	OldPath  string
-	OldLine  int
+	ShouldReport bool
+	LnumDiff     int  // 0 if the result is outside diff.
+	InDiffFile   bool // false if the result is outside diff files.
+	OldPath      string
+	OldLine      int
 }
 
 // FilterCheck filters check results by diff. It doesn't drop check which
-// is not in diff but set FilteredCheck.InDiff field false.
+// is not in diff but set FilteredCheck.ShouldReport field false.
 func FilterCheck(results []*CheckResult, diff []*diff.FileDiff, strip int,
 	cwd string, mode difffilter.Mode) []*FilteredCheck {
 	checks := make([]*FilteredCheck, 0, len(results))
 	df := difffilter.New(diff, strip, cwd, mode)
 	for _, result := range results {
 		check := &FilteredCheck{CheckResult: result}
-		if yes, diffline := df.InDiff(result.Path, result.Lnum); yes {
-			check.InDiff = true
-			if diffline != nil {
-				check.LnumDiff = diffline.LnumDiff
-			}
+		shouldReport, difffile, diffline := df.ShouldReport(result.Path, result.Lnum)
+		check.ShouldReport = shouldReport
+		if diffline != nil {
+			check.LnumDiff = diffline.LnumDiff
 		}
 		result.Path = CleanPath(result.Path, cwd)
-		check.OldPath, check.OldLine = getOldPosition(diff, strip, result.Path, result.Lnum)
+		if difffile != nil {
+			check.InDiffFile = true
+			check.OldPath, check.OldLine = getOldPosition(difffile, strip, result.Path, result.Lnum)
+		}
 		checks = append(checks, check)
 	}
 	return checks
@@ -54,25 +57,25 @@ func CleanPath(path, workdir string) string {
 	return filepath.ToSlash(p)
 }
 
-func getOldPosition(filediffs []*diff.FileDiff, strip int, newPath string, newLine int) (oldPath string, oldLine int) {
-	for _, filediff := range filediffs {
-		if difffilter.NormalizeDiffPath(filediff.PathNew, strip) != newPath {
-			continue
+func getOldPosition(filediff *diff.FileDiff, strip int, newPath string, newLine int) (oldPath string, oldLine int) {
+	if filediff == nil {
+		return "", 0
+	}
+	if difffilter.NormalizeDiffPath(filediff.PathNew, strip) != newPath {
+		return "", 0
+	}
+	oldPath = difffilter.NormalizeDiffPath(filediff.PathOld, strip)
+	delta := 0
+	for _, hunk := range filediff.Hunks {
+		if newLine < hunk.StartLineNew {
+			break
 		}
-		oldPath = difffilter.NormalizeDiffPath(filediff.PathOld, strip)
-		delta := 0
-		for _, hunk := range filediff.Hunks {
-			if newLine < hunk.StartLineNew {
-				break
-			}
-			delta += hunk.LineLengthOld - hunk.LineLengthNew
-			for _, line := range hunk.Lines {
-				if line.LnumNew == newLine {
-					return oldPath, line.LnumOld
-				}
+		delta += hunk.LineLengthOld - hunk.LineLengthNew
+		for _, line := range hunk.Lines {
+			if line.LnumNew == newLine {
+				return oldPath, line.LnumOld
 			}
 		}
-		return oldPath, newLine + delta
 	}
-	return "", 0
+	return oldPath, newLine + delta
 }
diff --git a/go.mod b/go.mod
index da9d26f5..967ac61b 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/kylelemons/godebug v1.1.0
 	github.com/mattn/go-shellwords v1.0.10
 	github.com/rakyll/statik v0.1.7
-	github.com/reviewdog/errorformat v0.0.0-20200504131036-9dd578dbe019
+	github.com/reviewdog/errorformat v0.0.0-20200502163639-f5b5a3cb8aed
 	github.com/xanzy/go-gitlab v0.31.0
 	golang.org/x/build v0.0.0-20200428202702-916311cec4e1
 	golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
diff --git a/go.sum b/go.sum
index 88734f1c..d6d45cee 100644
--- a/go.sum
+++ b/go.sum
@@ -153,8 +153,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
 github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
-github.com/reviewdog/errorformat v0.0.0-20200504131036-9dd578dbe019 h1:y6t2yXohyUG8adMyljvImMAN/81LSHQI06cdNkxMbR0=
-github.com/reviewdog/errorformat v0.0.0-20200504131036-9dd578dbe019/go.mod h1:giYAXnpegRDPsXUO7TRpDKXJo1lFGYxyWRfEt5iQ+OA=
+github.com/reviewdog/errorformat v0.0.0-20200502163639-f5b5a3cb8aed h1:loFcu5lrYzlfJzrp5oZGeK0X8LrwNDQXle248X89a8s=
+github.com/reviewdog/errorformat v0.0.0-20200502163639-f5b5a3cb8aed/go.mod h1:giYAXnpegRDPsXUO7TRpDKXJo1lFGYxyWRfEt5iQ+OA=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
diff --git a/reviewdog.go b/reviewdog.go
index 2d1e9077..c26711eb 100644
--- a/reviewdog.go
+++ b/reviewdog.go
@@ -52,13 +52,9 @@ type Parser interface {
 
 // Comment represents a reported result as a comment.
 type Comment struct {
-	*CheckResult
+	Result   *FilteredCheck
 	ToolName string
 	Body     string
-	LnumDiff int
-	DiffLine *diff.Line
-	OldPath  string
-	OldLine  int
 }
 
 // CommentService is an interface which posts Comment.
@@ -90,16 +86,13 @@ func (w *Reviewdog) runFromResult(ctx context.Context, results []*CheckResult,
 	hasViolations := false
 
 	for _, check := range checks {
-		if !check.InDiff {
+		if !check.ShouldReport {
 			continue
 		}
 		comment := &Comment{
-			CheckResult: check.CheckResult,
-			Body:        check.Message,
-			LnumDiff:    check.LnumDiff,
-			OldPath:     check.OldPath,
-			OldLine:     check.OldLine,
-			ToolName:    w.toolname,
+			Result:   check,
+			Body:     check.Message,
+			ToolName: w.toolname,
 		}
 		if err := w.c.Post(ctx, comment); err != nil {
 			return err
@@ -108,7 +101,9 @@ func (w *Reviewdog) runFromResult(ctx context.Context, results []*CheckResult,
 	}
 
 	if bulk, ok := w.c.(BulkCommentService); ok {
-		return bulk.Flush(ctx)
+		if err := bulk.Flush(ctx); err != nil {
+			return err
+		}
 	}
 
 	if failOnError && hasViolations {
diff --git a/service/commentutil/commentutil.go b/service/commentutil/commentutil.go
index da374079..6d6a0446 100644
--- a/service/commentutil/commentutil.go
+++ b/service/commentutil/commentutil.go
@@ -14,10 +14,10 @@ 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.Path]; !ok {
+	if _, ok := p[c.Result.Path]; !ok {
 		return false
 	}
-	bodies, ok := p[c.Path][lineNum]
+	bodies, ok := p[c.Result.Path][lineNum]
 	if !ok {
 		return false
 	}
diff --git a/service/gerrit/change_review.go b/service/gerrit/change_review.go
index 191ba7da..7fd1c1d6 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.Path = filepath.Join(g.wd, c.Path)
+	c.Result.Path = filepath.Join(g.wd, c.Result.Path)
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -68,8 +68,11 @@ func (g *ChangeReviewCommenter) postAllComments(ctx context.Context) error {
 		Comments: map[string][]gerrit.CommentInput{},
 	}
 	for _, c := range g.postComments {
-		review.Comments[c.Path] = append(review.Comments[c.Path], gerrit.CommentInput{
-			Line:    c.Lnum,
+		if !c.Result.InDiffFile {
+			continue
+		}
+		review.Comments[c.Result.Path] = append(review.Comments[c.Result.Path], gerrit.CommentInput{
+			Line:    c.Result.Lnum,
 			Message: c.Body,
 		})
 	}
diff --git a/service/github/github.go b/service/github/github.go
index 791c0ec3..9fa53e67 100644
--- a/service/github/github.go
+++ b/service/github/github.go
@@ -10,6 +10,7 @@ import (
 	"github.com/google/go-github/v31/github"
 
 	"github.com/reviewdog/reviewdog"
+	"github.com/reviewdog/reviewdog/cienv"
 	"github.com/reviewdog/reviewdog/service/commentutil"
 	"github.com/reviewdog/reviewdog/service/github/githubutils"
 	"github.com/reviewdog/reviewdog/service/serviceutil"
@@ -61,7 +62,7 @@ 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.Path = filepath.ToSlash(filepath.Join(g.wd, c.Path))
+	c.Result.Path = filepath.ToSlash(filepath.Join(g.wd, c.Result.Path))
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -83,7 +84,15 @@ func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error {
 	comments := make([]*github.DraftReviewComment, 0, len(g.postComments))
 	remaining := make([]*reviewdog.Comment, 0)
 	for _, c := range g.postComments {
-		if g.postedcs.IsPosted(c, c.LnumDiff) {
+		if c.Result.LnumDiff == 0 {
+			// GitHub Review API cannot report results outside diff. If it's running
+			// in GitHub Actions, fallback to GitHub Actions log as report .
+			if cienv.IsInGitHubAction() {
+				githubutils.ReportAsGitHubActionsLog(c.ToolName, "warning", c.Result.CheckResult)
+			}
+			continue
+		}
+		if g.postedcs.IsPosted(c, c.Result.LnumDiff) {
 			continue
 		}
 		// Only posts maxCommentsPerRequest comments per 1 request to avoid spammy
@@ -100,8 +109,8 @@ func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error {
 		}
 		cbody := commentutil.CommentBody(c)
 		comments = append(comments, &github.DraftReviewComment{
-			Path:     &c.Path,
-			Position: &c.LnumDiff,
+			Path:     &c.Result.Path,
+			Position: &c.Result.LnumDiff,
 			Body:     &cbody,
 		})
 	}
@@ -110,8 +119,6 @@ func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error {
 		return nil
 	}
 
-	// TODO(haya14busa): it might be useful to report overview results by "body"
-	// field.
 	review := &github.PullRequestReviewRequest{
 		CommitID: &g.sha,
 		Event:    github.String("COMMENT"),
@@ -138,7 +145,7 @@ func (g *GitHubPullRequest) remainingCommentsSummary(remaining []*reviewdog.Comm
 		sb.WriteString(fmt.Sprintf("<summary>%s</summary>\n", tool))
 		sb.WriteString("\n")
 		for _, c := range comments {
-			sb.WriteString(githubutils.LinkedMarkdownCheckResult(g.owner, g.repo, g.sha, c.CheckResult))
+			sb.WriteString(githubutils.LinkedMarkdownCheckResult(g.owner, g.repo, g.sha, c.Result.CheckResult))
 			sb.WriteString("\n")
 		}
 		sb.WriteString("</details>\n")
diff --git a/service/github/githubutils/comment_writer.go b/service/github/githubutils/comment_writer.go
index f7f2812a..03e3bc25 100644
--- a/service/github/githubutils/comment_writer.go
+++ b/service/github/githubutils/comment_writer.go
@@ -32,7 +32,7 @@ func (lw *GitHubActionLogWriter) Post(_ context.Context, c *reviewdog.Comment) e
 	if lw.reportNum == MaxLoggingAnnotationsPerStep {
 		WarnTooManyAnnotationOnce()
 	}
-	ReportAsGitHubActionsLog(c.ToolName, lw.level, c.CheckResult)
+	ReportAsGitHubActionsLog(c.ToolName, lw.level, c.Result.CheckResult)
 	return nil
 }
 
diff --git a/service/gitlab/gitlab_mr_commit.go b/service/gitlab/gitlab_mr_commit.go
index c8143de1..4175c4c6 100644
--- a/service/gitlab/gitlab_mr_commit.go
+++ b/service/gitlab/gitlab_mr_commit.go
@@ -57,7 +57,7 @@ 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.Path = filepath.ToSlash(filepath.Join(g.wd, c.Path))
+	c.Result.Path = filepath.ToSlash(filepath.Join(g.wd, c.Result.Path))
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -79,21 +79,21 @@ func (g *GitLabMergeRequestCommitCommenter) Flush(ctx context.Context) error {
 func (g *GitLabMergeRequestCommitCommenter) postCommentsForEach(ctx context.Context) error {
 	var eg errgroup.Group
 	for _, c := range g.postComments {
-		comment := c
-		if g.postedcs.IsPosted(comment, comment.Lnum) {
+		c := c
+		if !c.Result.InDiffFile || c.Result.Lnum == 0 || g.postedcs.IsPosted(c, c.Result.Lnum) {
 			continue
 		}
 		eg.Go(func() error {
-			commitID, err := g.getLastCommitsID(comment.Path, comment.Lnum)
+			commitID, err := g.getLastCommitsID(c.Result.Path, c.Result.Lnum)
 			if err != nil {
 				commitID = g.sha
 			}
-			body := commentutil.CommentBody(comment)
+			body := commentutil.CommentBody(c)
 			ltype := "new"
 			prcomment := &gitlab.PostCommitCommentOptions{
 				Note:     &body,
-				Path:     &comment.Path,
-				Line:     &comment.Lnum,
+				Path:     &c.Result.Path,
+				Line:     &c.Result.Lnum,
 				LineType: &ltype,
 			}
 			_, _, err = g.cli.Commits.PostCommitComment(g.projects, commitID, prcomment, gitlab.WithContext(ctx))
diff --git a/service/gitlab/gitlab_mr_discussion.go b/service/gitlab/gitlab_mr_discussion.go
index 1f587ab2..97bbcedd 100644
--- a/service/gitlab/gitlab_mr_discussion.go
+++ b/service/gitlab/gitlab_mr_discussion.go
@@ -51,7 +51,7 @@ 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.Path = filepath.Join(g.wd, c.Path)
+	c.Result.Path = filepath.Join(g.wd, c.Result.Path)
 	g.muComments.Lock()
 	defer g.muComments.Unlock()
 	g.postComments = append(g.postComments, c)
@@ -99,8 +99,8 @@ func (g *GitLabMergeRequestDiscussionCommenter) postCommentsForEach(ctx context.
 
 	var eg errgroup.Group
 	for _, c := range g.postComments {
-		comment := c
-		if postedcs.IsPosted(comment, comment.Lnum) {
+		c := c
+		if !c.Result.InDiffFile || c.Result.Lnum == 0 || postedcs.IsPosted(c, c.Result.Lnum) {
 			continue
 		}
 		eg.Go(func() error {
@@ -109,15 +109,15 @@ func (g *GitLabMergeRequestDiscussionCommenter) postCommentsForEach(ctx context.
 				HeadSHA:      g.sha,
 				BaseSHA:      targetBranch.Commit.ID,
 				PositionType: "text",
-				NewPath:      comment.Path,
-				NewLine:      comment.Lnum,
+				NewPath:      c.Result.Path,
+				NewLine:      c.Result.Lnum,
 			}
-			if comment.OldPath != "" && comment.OldLine != 0 {
-				pos.OldPath = comment.OldPath
-				pos.OldLine = comment.OldLine
+			if c.Result.OldPath != "" && c.Result.OldLine != 0 {
+				pos.OldPath = c.Result.OldPath
+				pos.OldLine = c.Result.OldLine
 			}
 			discussion := &gitlab.CreateMergeRequestDiscussionOptions{
-				Body:     gitlab.String(commentutil.CommentBody(comment)),
+				Body:     gitlab.String(commentutil.CommentBody(c)),
 				Position: pos,
 			}
 			_, _, err := g.cli.Discussions.CreateMergeRequestDiscussion(g.projects, g.pr, discussion)


Test patch:
diff --git a/_testdata/golint2.go b/_testdata/golint2.go
new file mode 100644
index 00000000..766d3598
--- /dev/null
+++ b/_testdata/golint2.go
@@ -0,0 +1,3 @@
+package testdata
+
+var Golint2 = 2
diff --git a/cmd/reviewdog/doghouse_test.go b/cmd/reviewdog/doghouse_test.go
index b4d29d2c..355b208d 100644
--- a/cmd/reviewdog/doghouse_test.go
+++ b/cmd/reviewdog/doghouse_test.go
@@ -397,13 +397,13 @@ func TestReportResults(t *testing.T) {
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1-L1", "name1-L2"},
 				},
-				InDiff: true,
+				ShouldReport: true,
 			},
 			{
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1.2-L1", "name1.2-L2"},
 				},
-				InDiff: false,
+				ShouldReport: false,
 			},
 		},
 	})
@@ -413,14 +413,14 @@ func TestReportResults(t *testing.T) {
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1-L1", "name1-L2"},
 				},
-				InDiff: false,
+				ShouldReport: false,
 			},
 		},
 	})
 	stdout := new(bytes.Buffer)
-	foundResultInDiff := reportResults(stdout, filteredResultSet)
-	if !foundResultInDiff {
-		t.Errorf("foundResultInDiff = %v, want true", foundResultInDiff)
+	foundResultShouldReport := reportResults(stdout, filteredResultSet)
+	if !foundResultShouldReport {
+		t.Errorf("foundResultShouldReport = %v, want true", foundResultShouldReport)
 	}
 	want := `reviewdog: Reporting results for "name1"
 name1-L1
@@ -446,7 +446,7 @@ func TestReportResults_inGitHubAction(t *testing.T) {
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1-L1", "name1-L2"},
 				},
-				InDiff: true,
+				ShouldReport: true,
 			},
 		},
 	})
@@ -459,7 +459,7 @@ func TestReportResults_inGitHubAction(t *testing.T) {
 	}
 }
 
-func TestReportResults_noResultsInDiff(t *testing.T) {
+func TestReportResults_noResultsShouldReport(t *testing.T) {
 	cleanup := setupEnvs(map[string]string{
 		"GITHUB_ACTION":     "",
 		"GITHUB_EVENT_PATH": "",
@@ -472,13 +472,13 @@ func TestReportResults_noResultsInDiff(t *testing.T) {
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1-L1", "name1-L2"},
 				},
-				InDiff: false,
+				ShouldReport: false,
 			},
 			{
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1.2-L1", "name1.2-L2"},
 				},
-				InDiff: false,
+				ShouldReport: false,
 			},
 		},
 	})
@@ -488,14 +488,14 @@ func TestReportResults_noResultsInDiff(t *testing.T) {
 				CheckResult: &reviewdog.CheckResult{
 					Lines: []string{"name1-L1", "name1-L2"},
 				},
-				InDiff: false,
+				ShouldReport: false,
 			},
 		},
 	})
 	stdout := new(bytes.Buffer)
-	foundResultInDiff := reportResults(stdout, filteredResultSet)
-	if foundResultInDiff {
-		t.Errorf("foundResultInDiff = %v, want false", foundResultInDiff)
+	foundResultShouldReport := reportResults(stdout, filteredResultSet)
+	if foundResultShouldReport {
+		t.Errorf("foundResultShouldReport = %v, want false", foundResultShouldReport)
 	}
 	want := `reviewdog: Reporting results for "name1"
 reviewdog: No results found for "name1". 2 results found outside diff.
diff --git a/comment_iowriter_test.go b/comment_iowriter_test.go
index e8153397..5084caf4 100644
--- a/comment_iowriter_test.go
+++ b/comment_iowriter_test.go
@@ -14,9 +14,9 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 	}{
 		{
 			in: &Comment{
-				CheckResult: &CheckResult{
+				Result: &FilteredCheck{CheckResult: &CheckResult{
 					Path: "/path/to/file",
-				},
+				}},
 				ToolName: "tool name",
 				Body:     "message",
 			},
@@ -24,10 +24,10 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		},
 		{
 			in: &Comment{
-				CheckResult: &CheckResult{
+				Result: &FilteredCheck{CheckResult: &CheckResult{
 					Path: "/path/to/file",
 					Col:  14,
-				},
+				}},
 				ToolName: "tool name",
 				Body:     "message",
 			},
@@ -35,10 +35,10 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		},
 		{
 			in: &Comment{
-				CheckResult: &CheckResult{
+				Result: &FilteredCheck{CheckResult: &CheckResult{
 					Path: "/path/to/file",
 					Lnum: 14,
-				},
+				}},
 				ToolName: "tool name",
 				Body:     "message",
 			},
@@ -46,11 +46,11 @@ func TestUnifiedCommentWriter_Post(t *testing.T) {
 		},
 		{
 			in: &Comment{
-				CheckResult: &CheckResult{
+				Result: &FilteredCheck{CheckResult: &CheckResult{
 					Path: "/path/to/file",
 					Lnum: 14,
 					Col:  7,
-				},
+				}},
 				ToolName: "tool name",
 				Body:     "line1\nline2",
 			},
diff --git a/comment_test.go b/comment_test.go
index a97ae61c..66fb53f7 100644
--- a/comment_test.go
+++ b/comment_test.go
@@ -14,7 +14,7 @@ func TestMultiCommentService_Post(t *testing.T) {
 
 	const want = "line1\nline2"
 
-	c := &Comment{CheckResult: &CheckResult{Lines: strings.Split(want, "\n")}}
+	c := &Comment{Result: &FilteredCheck{CheckResult: &CheckResult{Lines: strings.Split(want, "\n")}}}
 	if err := w.Post(context.Background(), c); err != nil {
 		t.Fatal(err)
 	}
diff --git a/difffilter/filter_test.go b/difffilter/filter_test.go
index 4f78bf72..64ba17cd 100644
--- a/difffilter/filter_test.go
+++ b/difffilter/filter_test.go
@@ -20,6 +20,7 @@ func TestMode_Set(t *testing.T) {
 		{value: "added", want: ModeAdded},
 		{value: "diff_context", want: ModeDiffContext},
 		{value: "file", want: ModeFile},
+		{value: "nofilter", want: ModeNoFilter},
 		{value: "unknown", wantErr: true},
 	}
 	for _, tt := range tests {
@@ -109,60 +110,112 @@ func TestDiffFilter_root(t *testing.T) {
 		lnum         int
 		mode         Mode
 		want         bool
-		wantLnumDiff int
+		wantFileDiff bool
+		wantLineDiff bool
 	}{
 		{
 			path:         "sample.new.txt",
 			lnum:         2,
 			mode:         ModeAdded,
 			want:         true,
-			wantLnumDiff: 3,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         filepath.Join(getCwd(), "sample.new.txt"),
 			lnum:         2,
 			mode:         ModeAdded,
 			want:         true,
-			wantLnumDiff: 3,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         "sample.new.txt",
 			lnum:         1,
 			mode:         ModeAdded,
 			want:         false,
-			wantLnumDiff: 0,
+			wantFileDiff: true,
+			wantLineDiff: false,
 		},
 		{
 			path:         "sample.new.txt",
 			lnum:         1,
 			mode:         ModeDiffContext,
 			want:         true,
-			wantLnumDiff: 1,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         "subdir/nonewline.new.txt",
 			lnum:         3,
 			mode:         ModeAdded,
 			want:         true,
-			wantLnumDiff: 3,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         "sample.new.txt",
 			lnum:         14,
 			mode:         ModeFile,
 			want:         true,
-			wantLnumDiff: 0,
+			wantFileDiff: true,
+			wantLineDiff: false,
+		},
+		{
+			path:         "sample.new.txt",
+			lnum:         0, // Only file path.
+			mode:         ModeFile,
+			want:         true,
+			wantFileDiff: true,
+			wantLineDiff: false,
+		},
+		{
+			path:         "sample.new.txt",
+			lnum:         0, // Only file path.
+			mode:         ModeAdded,
+			want:         false,
+			wantFileDiff: true,
+			wantLineDiff: false,
+		},
+		{
+			path:         "sample.new.txt",
+			lnum:         2,
+			mode:         ModeNoFilter,
+			want:         true,
+			wantFileDiff: true,
+			wantLineDiff: true,
+		},
+		{
+			path:         "sample.new.txt",
+			lnum:         2,
+			mode:         ModeNoFilter,
+			want:         true,
+			wantFileDiff: true,
+			wantLineDiff: true, // ModeNoFilter returns linediff if possible.
+		},
+		{
+			path:         "any_path_with_any_line.txt",
+			lnum:         141414,
+			mode:         ModeNoFilter,
+			want:         true,
+			wantFileDiff: false,
+			wantLineDiff: false,
+		},
+		{
+			path:         "any_path_only.txt",
+			mode:         ModeNoFilter,
+			want:         true,
+			wantFileDiff: false,
+			wantLineDiff: false,
 		},
 	}
 	for _, tt := range tests {
 		df := New(files, 1, getCwd(), tt.mode)
-		if got, gotLine := df.InDiff(tt.path, tt.lnum); got != tt.want {
-			gotLnumDiff := 0
-			if gotLine != nil {
-				gotLnumDiff = gotLine.LnumDiff
-			}
-			t.Errorf("InDiff(%q, %d) = (%v, %d), want (%v, %d)",
-				tt.path, tt.lnum, got, gotLnumDiff, tt.want, tt.wantLnumDiff)
+		if got, gotFile, gotLine := df.ShouldReport(tt.path, tt.lnum); got != tt.want ||
+			(gotFile != nil) != tt.wantFileDiff ||
+			(gotLine != nil) != tt.wantLineDiff {
+			t.Errorf("[%s] ShouldReport(%q, %d) = (%v, %t, %t), want (%v, %t, %t)",
+				tt.mode.String(), tt.path, tt.lnum, got, gotFile != nil, gotLine != nil, tt.want, tt.wantFileDiff, tt.wantLineDiff)
 		}
 	}
 }
@@ -177,46 +230,49 @@ func TestDiffFilter_subdir(t *testing.T) {
 		lnum         int
 		mode         Mode
 		want         bool
-		wantLnumDiff int
+		wantLineDiff bool
+		wantFileDiff bool
 	}{
 		{
 			path:         "sample.new.txt",
 			lnum:         2,
 			mode:         ModeAdded,
 			want:         true,
-			wantLnumDiff: 3,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         "sample.new.txt",
 			lnum:         2,
 			mode:         ModeDefault,
 			want:         true,
-			wantLnumDiff: 3,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         filepath.Join(getCwd(), "sample.new.txt"),
 			lnum:         2,
 			mode:         ModeAdded,
 			want:         true,
-			wantLnumDiff: 3,
+			wantFileDiff: true,
+			wantLineDiff: true,
 		},
 		{
 			path:         "sample.new.txt",
 			lnum:         5,
 			mode:         ModeAdded,
 			want:         false,
-			wantLnumDiff: 0,
+			wantFileDiff: true,
+			wantLineDiff: false,
 		},
 	}
 	for _, tt := range tests {
 		df := New(files, 1, getCwd(), tt.mode)
-		if got, gotLine := df.InDiff(tt.path, tt.lnum); got != tt.want {
-			gotLnumDiff := 0
-			if gotLine != nil {
-				gotLnumDiff = gotLine.LnumDiff
-			}
-			t.Errorf("InDiff(%q, %d) = (%v, %d), want (%v, %d)",
-				tt.path, tt.lnum, got, gotLnumDiff, tt.want, tt.wantLnumDiff)
+		if got, gotFile, gotLine := df.ShouldReport(tt.path, tt.lnum); got != tt.want ||
+			(gotFile != nil) != tt.wantFileDiff ||
+			(gotLine != nil) != tt.wantLineDiff {
+			t.Errorf("ShouldReport(%q, %d) = (%v, %t, %t), want (%v, %t, %t)",
+				tt.path, tt.lnum, got, gotFile != nil, gotLine != nil, tt.want, tt.wantFileDiff, tt.wantLineDiff)
 		}
 	}
 }
diff --git a/filter_test.go b/filter_test.go
index 7c4dbe25..ffbe81a3 100644
--- a/filter_test.go
+++ b/filter_test.go
@@ -67,38 +67,42 @@ func TestFilterCheckByAddedLines(t *testing.T) {
 				Path: "sample.new.txt",
 				Lnum: 1,
 			},
-			InDiff:  false,
-			OldPath: "sample.old.txt",
-			OldLine: 1,
+			ShouldReport: false,
+			InDiffFile:   true,
+			OldPath:      "sample.old.txt",
+			OldLine:      1,
 		},
 		{
 			CheckResult: &CheckResult{
 				Path: "sample.new.txt",
 				Lnum: 2,
 			},
-			InDiff:   true,
-			LnumDiff: 3,
-			OldPath:  "sample.old.txt",
-			OldLine:  0,
+			ShouldReport: true,
+			InDiffFile:   true,
+			LnumDiff:     3,
+			OldPath:      "sample.old.txt",
+			OldLine:      0,
 		},
 		{
 			CheckResult: &CheckResult{
 				Path: "nonewline.new.txt",
 				Lnum: 1,
 			},
-			InDiff:  false,
-			OldPath: "nonewline.old.txt",
-			OldLine: 1,
+			ShouldReport: false,
+			InDiffFile:   true,
+			OldPath:      "nonewline.old.txt",
+			OldLine:      1,
 		},
 		{
 			CheckResult: &CheckResult{
 				Path: "nonewline.new.txt",
 				Lnum: 3,
 			},
-			InDiff:   true,
-			LnumDiff: 5,
-			OldPath:  "nonewline.old.txt",
-			OldLine:  0,
+			ShouldReport: true,
+			InDiffFile:   true,
+			LnumDiff:     5,
+			OldPath:      "nonewline.old.txt",
+			OldLine:      0,
 		},
 	}
 	filediffs, _ := diff.ParseMultiFile(strings.NewReader(diffContent))
@@ -130,30 +134,33 @@ func TestFilterCheckByDiffContext(t *testing.T) {
 				Path: "sample.new.txt",
 				Lnum: 1,
 			},
-			InDiff:   true,
-			LnumDiff: 1,
-			OldPath:  "sample.old.txt",
-			OldLine:  1,
+			ShouldReport: true,
+			InDiffFile:   true,
+			LnumDiff:     1,
+			OldPath:      "sample.old.txt",
+			OldLine:      1,
 		},
 		{
 			CheckResult: &CheckResult{
 				Path: "sample.new.txt",
 				Lnum: 2,
 			},
-			InDiff:   true,
-			LnumDiff: 3,
-			OldPath:  "sample.old.txt",
-			OldLine:  0,
+			ShouldReport: true,
+			InDiffFile:   true,
+			LnumDiff:     3,
+			OldPath:      "sample.old.txt",
+			OldLine:      0,
 		},
 		{
 			CheckResult: &CheckResult{
 				Path: "sample.new.txt",
 				Lnum: 3,
 			},
-			InDiff:   true,
-			LnumDiff: 4,
-			OldPath:  "sample.old.txt",
-			OldLine:  0,
+			ShouldReport: true,
+			InDiffFile:   true,
+			LnumDiff:     4,
+			OldPath:      "sample.old.txt",
+			OldLine:      0,
 		},
 	}
 	filediffs, _ := diff.ParseMultiFile(strings.NewReader(diffContent))
@@ -163,6 +170,15 @@ func TestFilterCheckByDiffContext(t *testing.T) {
 	}
 }
 
+func findFileDiff(filediffs []*diff.FileDiff, path string, strip int) *diff.FileDiff {
+	for _, file := range filediffs {
+		if difffilter.NormalizeDiffPath(file.PathNew, strip) == path {
+			return file
+		}
+	}
+	return nil
+}
+
 func TestGetOldPosition(t *testing.T) {
 	const strip = 0
 	filediffs, _ := diff.ParseMultiFile(strings.NewReader(diffContent))
@@ -204,7 +220,8 @@ func TestGetOldPosition(t *testing.T) {
 		},
 	}
 	for _, tt := range tests {
-		gotPath, gotLine := getOldPosition(filediffs, strip, tt.newPath, tt.newLine)
+		fdiff := findFileDiff(filediffs, tt.newPath, strip)
+		gotPath, gotLine := getOldPosition(fdiff, strip, tt.newPath, tt.newLine)
 		if !(gotPath == tt.wantOldPath && gotLine == tt.wantOldLine) {
 			t.Errorf("getOldPosition(..., %s, %d) = (%s, %d), want (%s, %d)",
 				tt.newPath, tt.newLine, gotPath, gotLine, tt.wantOldPath, tt.wantOldLine)
@@ -215,7 +232,9 @@ func TestGetOldPosition(t *testing.T) {
 func TestGetOldPosition_added(t *testing.T) {
 	const strip = 1
 	filediffs, _ := diff.ParseMultiFile(strings.NewReader(diffContentAddedStrip))
-	gotPath, _ := getOldPosition(filediffs, strip, "test_added.go", 1)
+	path := "test_added.go"
+	fdiff := findFileDiff(filediffs, path, strip)
+	gotPath, _ := getOldPosition(fdiff, strip, path, 1)
 	if gotPath != "" {
 		t.Errorf("got %q as old path for addedd diff file, want empty", gotPath)
 	}
diff --git a/reviewdog_test.go b/reviewdog_test.go
index 0fbc6a49..4f383cd4 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.Path; got != want {
+			if got := c.Result.Path; 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 bc6facd6..1bb1f80a 100644
--- a/service/gerrit/change_review_test.go
+++ b/service/gerrit/change_review_test.go
@@ -28,24 +28,32 @@ func TestChangeReviewCommenter_Post_Flush(t *testing.T) {
 	ctx := context.Background()
 	newLnum1 := 14
 	newComment1 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 			Path: "file.go",
 			Lnum: newLnum1,
-		},
+		}, InDiffFile: true},
 		Body: "new comment",
 	}
 	newLnum2 := 15
 	newComment2 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 			Path: "file2.go",
 			Lnum: newLnum2,
-		},
+		}, InDiffFile: true},
 		Body: "new comment 2",
 	}
+	commentOutsideDiff := &reviewdog.Comment{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+			Path: "file3.go",
+			Lnum: 14,
+		}, InDiffFile: false},
+		Body: "comment outside diff",
+	}
 
 	comments := []*reviewdog.Comment{
 		newComment1,
 		newComment2,
+		commentOutsideDiff,
 	}
 
 	mux := http.NewServeMux()
@@ -57,16 +65,16 @@ func TestChangeReviewCommenter_Post_Flush(t *testing.T) {
 				t.Error(err)
 			}
 
-			if len(got.Comments) != len(comments) {
-				t.Error("expected two comments")
+			if want := len(comments) - 1; len(got.Comments) != want {
+				t.Errorf("got %d comments, want %d", len(got.Comments), want)
 			}
 
-			want := []gerrit.CommentInput{{Line: newComment1.Lnum, Message: newComment1.Body}}
+			want := []gerrit.CommentInput{{Line: newComment1.Result.Lnum, Message: newComment1.Body}}
 			if diff := cmp.Diff(got.Comments["file.go"], want); diff != "" {
 				t.Error(diff)
 			}
 
-			want = []gerrit.CommentInput{{Line: newComment2.Lnum, Message: newComment2.Body}}
+			want = []gerrit.CommentInput{{Line: newComment2.Result.Lnum, 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 356653a3..909f39b1 100644
--- a/service/github/github_test.go
+++ b/service/github/github_test.go
@@ -32,6 +32,22 @@ func setupGitHubClient() *github.Client {
 	return github.NewClient(tc)
 }
 
+func setupEnvs() (cleanup func()) {
+	var cleanEnvs = []string{
+		"GITHUB_ACTION",
+	}
+	saveEnvs := make(map[string]string)
+	for _, key := range cleanEnvs {
+		saveEnvs[key] = os.Getenv(key)
+		os.Unsetenv(key)
+	}
+	return func() {
+		for key, value := range saveEnvs {
+			os.Setenv(key, value)
+		}
+	}
+}
+
 func moveToRootDir() {
 	os.Chdir("../..")
 }
@@ -54,11 +70,13 @@ func TestGitHubPullRequest_Post(t *testing.T) {
 		t.Fatal(err)
 	}
 	comment := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
-			Path: "watchdogs.go",
+		Result: &reviewdog.FilteredCheck{
+			CheckResult: &reviewdog.CheckResult{
+				Path: "watchdogs.go",
+			},
+			LnumDiff: 17,
 		},
-		LnumDiff: 17,
-		Body:     "[reviewdog] test",
+		Body: "[reviewdog] test",
 	}
 	// https://github.com/reviewdog/reviewdog/pull/2/files#diff-ed1d019a10f54464cfaeaf6a736b7d27L20
 	if err := g.Post(context.Background(), comment); err != nil {
@@ -175,6 +193,7 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 	cwd, _ := os.Getwd()
 	defer os.Chdir(cwd)
 	moveToRootDir()
+	defer setupEnvs()()
 
 	listCommentsAPICalled := 0
 	postCommentsAPICalled := 0
@@ -250,25 +269,40 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 	}
 	comments := []*reviewdog.Comment{
 		{
-			CheckResult: &reviewdog.CheckResult{
-				Path: "reviewdog.go",
+			Result: &reviewdog.FilteredCheck{
+				CheckResult: &reviewdog.CheckResult{
+					Path: "reviewdog.go",
+				},
+				LnumDiff: 1,
 			},
-			LnumDiff: 1,
-			Body:     "already commented",
+			Body: "already commented",
 		},
 		{
-			CheckResult: &reviewdog.CheckResult{
-				Path: "reviewdog.go",
+			Result: &reviewdog.FilteredCheck{
+				CheckResult: &reviewdog.CheckResult{
+					Path: "reviewdog.go",
+				},
+				LnumDiff: 14,
 			},
-			LnumDiff: 14,
-			Body:     "already commented 2",
+			Body: "already commented 2",
 		},
 		{
-			CheckResult: &reviewdog.CheckResult{
-				Path: "reviewdog.go",
+			Result: &reviewdog.FilteredCheck{
+				CheckResult: &reviewdog.CheckResult{
+					Path: "reviewdog.go",
+				},
+				LnumDiff: 14,
 			},
-			LnumDiff: 14,
-			Body:     "new comment",
+			Body: "new comment",
+		},
+		{
+			Result: &reviewdog.FilteredCheck{
+				CheckResult: &reviewdog.CheckResult{
+					Path: "reviewdog.go",
+				},
+				// No LnumDiff.
+			},
+			Body: "should not be reported via GitHub Review API",
 		},
 	}
 	for _, c := range comments {
@@ -290,8 +324,8 @@ func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
 func TestGitHubPullRequest_Post_toomany(t *testing.T) {
 	cwd, _ := os.Getwd()
 	defer os.Chdir(cwd)
-
 	moveToRootDir()
+	defer setupEnvs()()
 
 	listCommentsAPICalled := 0
 	postCommentsAPICalled := 0
@@ -325,11 +359,13 @@ func TestGitHubPullRequest_Post_toomany(t *testing.T) {
 	var comments []*reviewdog.Comment
 	for i := 0; i < 100; i++ {
 		comments = append(comments, &reviewdog.Comment{
-			CheckResult: &reviewdog.CheckResult{
-				Path: "reviewdog.go",
-				Lnum: i,
+			Result: &reviewdog.FilteredCheck{
+				CheckResult: &reviewdog.CheckResult{
+					Path: "reviewdog.go",
+					Lnum: i,
+				},
+				LnumDiff: i,
 			},
-			LnumDiff: i,
 			Body:     "comment",
 			ToolName: "tool",
 		})
@@ -354,6 +390,7 @@ func TestGitHubPullRequest_workdir(t *testing.T) {
 	cwd, _ := os.Getwd()
 	defer os.Chdir(cwd)
 	moveToRootDir()
+	defer setupEnvs()()
 
 	g, err := NewGitHubPullRequest(nil, "", "", 0, "")
 	if err != nil {
@@ -364,8 +401,8 @@ func TestGitHubPullRequest_workdir(t *testing.T) {
 	}
 	ctx := context.Background()
 	want := "a/b/c"
-	g.Post(ctx, &reviewdog.Comment{CheckResult: &reviewdog.CheckResult{Path: want}})
-	if got := g.postComments[0].Path; got != want {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: want}}})
+	if got := g.postComments[0].Result.Path; got != want {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, want)
 	}
 
@@ -379,8 +416,8 @@ func TestGitHubPullRequest_workdir(t *testing.T) {
 	}
 	path := "a/b/c"
 	wantPath := "cmd/" + path
-	g.Post(ctx, &reviewdog.Comment{CheckResult: &reviewdog.CheckResult{Path: path}})
-	if got := g.postComments[0].Path; got != wantPath {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: path}}})
+	if got := g.postComments[0].Result.Path; got != wantPath {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, wantPath)
 	}
 }
diff --git a/service/gitlab/gitlab_mr_commit_test.go b/service/gitlab/gitlab_mr_commit_test.go
index 70701841..a64c179d 100644
--- a/service/gitlab/gitlab_mr_commit_test.go
+++ b/service/gitlab/gitlab_mr_commit_test.go
@@ -91,17 +91,17 @@ func TestGitLabMergeRequestCommitCommenter_Post_Flush_review_api(t *testing.T) {
 	// If setting exists file path, sha is changed by last commit id.
 	comments := []*reviewdog.Comment{
 		{
-			CheckResult: &reviewdog.CheckResult{
+			Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 				Path: "notExistFile.go",
 				Lnum: 1,
-			},
+			}, InDiffFile: true},
 			Body: "already commented",
 		},
 		{
-			CheckResult: &reviewdog.CheckResult{
+			Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 				Path: "notExistFile.go",
 				Lnum: 14,
-			},
+			}, InDiffFile: true},
 			Body: "new comment",
 		},
 	}
@@ -132,8 +132,8 @@ func TestGitLabPullRequest_workdir(t *testing.T) {
 	}
 	ctx := context.Background()
 	want := "a/b/c"
-	g.Post(ctx, &reviewdog.Comment{CheckResult: &reviewdog.CheckResult{Path: want}})
-	if got := g.postComments[0].Path; got != want {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: want}}})
+	if got := g.postComments[0].Result.Path; got != want {
 		t.Errorf("wd=%q path=%q, want %q", g.wd, got, want)
 	}
 
@@ -147,8 +147,8 @@ func TestGitLabPullRequest_workdir(t *testing.T) {
 	}
 	path := "a/b/c"
 	wantPath := "cmd/" + path
-	g.Post(ctx, &reviewdog.Comment{CheckResult: &reviewdog.CheckResult{Path: path}})
-	if got := g.postComments[0].Path; got != wantPath {
+	g.Post(ctx, &reviewdog.Comment{Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{Path: path}}})
+	if got := g.postComments[0].Result.Path; 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 6dc0e123..40751de9 100644
--- a/service/gitlab/gitlab_mr_discussion_test.go
+++ b/service/gitlab/gitlab_mr_discussion_test.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"os"
+	"sync/atomic"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -21,41 +22,58 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 	os.Chdir("../..")
 
 	alreadyCommented1 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 			Path: "file.go",
 			Lnum: 1,
-		},
+		}, InDiffFile: true},
 		Body: "already commented",
 	}
 	alreadyCommented2 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 			Path: "another/file.go",
 			Lnum: 14,
-		},
+		}, InDiffFile: true},
 		Body: "already commented 2",
 	}
 	newComment1 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 			Path: "file.go",
 			Lnum: 14,
-		},
+		}, InDiffFile: true},
 		Body: "new comment",
 	}
 	newComment2 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
 			Path: "file2.go",
 			Lnum: 15,
-		},
+		}, InDiffFile: true},
 		Body: "new comment 2",
 	}
 	newComment3 := &reviewdog.Comment{
-		CheckResult: &reviewdog.CheckResult{
-			Path: "new_file.go",
-			Lnum: 14,
+		Result: &reviewdog.FilteredCheck{
+			CheckResult: &reviewdog.CheckResult{
+				Path: "new_file.go",
+				Lnum: 14,
+			},
+			OldPath:    "old_file.go",
+			OldLine:    7,
+			InDiffFile: true,
 		},
-		Body:    "new comment 3",
-		OldPath: "old_file.go",
-		OldLine: 7,
+		Body: "new comment 3",
+	}
+	commentOutsideDiff := &reviewdog.Comment{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+			Path: "path.go",
+			Lnum: 14,
+		}, InDiffFile: false},
+		Body: "comment outside diff",
+	}
+	commentWithoutLnum := &reviewdog.Comment{
+		Result: &reviewdog.FilteredCheck{CheckResult: &reviewdog.CheckResult{
+			Path: "path.go",
+			Lnum: 0,
+		}, InDiffFile: true},
+		Body: "comment without lnum",
 	}
 
 	comments := []*reviewdog.Comment{
@@ -64,7 +82,11 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 		newComment1,
 		newComment2,
 		newComment3,
+		commentOutsideDiff,
+		commentWithoutLnum,
 	}
+	var postCalled int32
+	const wantPostCalled = 3
 
 	mux := http.NewServeMux()
 	mux.HandleFunc("/api/v4/projects/o/r/merge_requests/14/discussions", func(w http.ResponseWriter, r *http.Request) {
@@ -78,8 +100,8 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 							{
 								Body: commentutil.CommentBody(alreadyCommented1),
 								Position: &gitlab.NotePosition{
-									NewPath: alreadyCommented1.Path,
-									NewLine: alreadyCommented1.Lnum,
+									NewPath: alreadyCommented1.Result.Path,
+									NewLine: alreadyCommented1.Result.Lnum,
 								},
 							},
 							{
@@ -103,8 +125,8 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 							{
 								Body: commentutil.CommentBody(alreadyCommented2),
 								Position: &gitlab.NotePosition{
-									NewPath: alreadyCommented2.Path,
-									NewLine: alreadyCommented2.Lnum,
+									NewPath: alreadyCommented2.Result.Path,
+									NewLine: alreadyCommented2.Result.Lnum,
 								},
 							},
 						},
@@ -116,6 +138,7 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 			}
 
 		case http.MethodPost:
+			atomic.AddInt32(&postCalled, 1)
 			got := new(gitlab.CreateMergeRequestDiscussionOptions)
 			if err := json.NewDecoder(r.Body).Decode(got); err != nil {
 				t.Error(err)
@@ -195,4 +218,7 @@ func TestGitLabMergeRequestDiscussionCommenter_Post_Flush_review_api(t *testing.
 	if err := g.Flush(context.Background()); err != nil {
 		t.Errorf("%v", err)
 	}
+	if postCalled != wantPostCalled {
+		t.Errorf("%d discussions posted, but want %d", postCalled, wantPostCalled)
+	}
 }