github.com/mistwind/reviewdog@v0.0.0-20230322024206-9cfa11856d58/filter/filter.go (about)

     1  package filter
     2  
     3  import (
     4  	"path/filepath"
     5  
     6  	"github.com/mistwind/reviewdog/diff"
     7  	"github.com/mistwind/reviewdog/proto/rdf"
     8  )
     9  
    10  // FilteredDiagnostic represents Diagnostic with filtering info.
    11  type FilteredDiagnostic struct {
    12  	Diagnostic   *rdf.Diagnostic
    13  	ShouldReport bool
    14  	// false if the result is outside diff files.
    15  	InDiffFile bool
    16  	// true if the result is inside a diff hunk.
    17  	// If it's a multiline result, both start and end must be in the same diff
    18  	// hunk.
    19  	InDiffContext bool
    20  
    21  	// Similar to InDiffContext but for suggestion. True if first
    22  	// suggestion is in diff context.
    23  	FirstSuggestionInDiffContext bool
    24  
    25  	// Source lines text of the diagnostic message's line-range. Key is line
    26  	// number. If a suggestion range is broader than the diagnostic message's
    27  	// line-range, suggestions' line-range are included too.  It contains a whole
    28  	// line even if the diagnostic range have column fields.
    29  	// Optional. Currently available only when it's in diff context.
    30  	SourceLines map[int]string
    31  
    32  	OldPath string
    33  	OldLine int
    34  }
    35  
    36  // FilterCheck filters check results by diff. It doesn't drop check which
    37  // is not in diff but set FilteredDiagnostic.ShouldReport field false.
    38  func FilterCheck(results []*rdf.Diagnostic, diff []*diff.FileDiff, strip int,
    39  	cwd string, mode Mode) []*FilteredDiagnostic {
    40  	checks := make([]*FilteredDiagnostic, 0, len(results))
    41  	df := NewDiffFilter(diff, strip, cwd, mode)
    42  	for _, result := range results {
    43  		check := &FilteredDiagnostic{Diagnostic: result, SourceLines: make(map[int]string)}
    44  		loc := result.GetLocation()
    45  		loc.Path = NormalizePath(loc.GetPath(), cwd, "")
    46  		startLine := int(loc.GetRange().GetStart().GetLine())
    47  		endLine := int(loc.GetRange().GetEnd().GetLine())
    48  		if endLine == 0 {
    49  			endLine = startLine
    50  		}
    51  		check.InDiffContext = true
    52  		for l := startLine; l <= endLine; l++ {
    53  			shouldReport, difffile, diffline := df.ShouldReport(loc.GetPath(), l)
    54  			check.ShouldReport = check.ShouldReport || shouldReport
    55  			// all lines must be in diff.
    56  			check.InDiffContext = check.InDiffContext && diffline != nil
    57  			if diffline != nil {
    58  				check.SourceLines[l] = diffline.Content
    59  			}
    60  			if difffile != nil {
    61  				check.InDiffFile = true
    62  				if l == startLine {
    63  					// TODO(haya14busa): Support endline as well especially for GitLab.
    64  					check.OldPath, check.OldLine = getOldPosition(difffile, strip, loc.GetPath(), l)
    65  				}
    66  			}
    67  		}
    68  		// Add source lines for suggestions.
    69  		for i, s := range result.GetSuggestions() {
    70  			inDiffContext := true
    71  			start := int(s.GetRange().GetStart().GetLine())
    72  			end := int(s.GetRange().GetEnd().GetLine())
    73  			for l := start; l <= end; l++ {
    74  				if diffline := df.DiffLine(loc.GetPath(), l); diffline != nil {
    75  					check.SourceLines[l] = diffline.Content
    76  				} else {
    77  					inDiffContext = false
    78  				}
    79  			}
    80  			if i == 0 {
    81  				check.FirstSuggestionInDiffContext = inDiffContext
    82  			}
    83  		}
    84  		checks = append(checks, check)
    85  	}
    86  	return checks
    87  }
    88  
    89  // NormalizePath return normalized path with workdir and relative path to
    90  // project.
    91  func NormalizePath(path, workdir, projectRelPath string) string {
    92  	path = filepath.Clean(path)
    93  	if path == "." {
    94  		return ""
    95  	}
    96  	// Convert absolute path to relative path only if the path is in current
    97  	// directory.
    98  	if filepath.IsAbs(path) && workdir != "" && contains(path, workdir) {
    99  		relPath, err := filepath.Rel(workdir, path)
   100  		if err == nil {
   101  			path = relPath
   102  		}
   103  	}
   104  	if !filepath.IsAbs(path) && projectRelPath != "" {
   105  		path = filepath.Join(projectRelPath, path)
   106  	}
   107  	return filepath.ToSlash(path)
   108  }
   109  
   110  func getOldPosition(filediff *diff.FileDiff, strip int, newPath string, newLine int) (oldPath string, oldLine int) {
   111  	if filediff == nil {
   112  		return "", 0
   113  	}
   114  	if NormalizeDiffPath(filediff.PathNew, strip) != newPath {
   115  		return "", 0
   116  	}
   117  	oldPath = NormalizeDiffPath(filediff.PathOld, strip)
   118  	delta := 0
   119  	for _, hunk := range filediff.Hunks {
   120  		if newLine < hunk.StartLineNew {
   121  			break
   122  		}
   123  		delta += hunk.LineLengthOld - hunk.LineLengthNew
   124  		for _, line := range hunk.Lines {
   125  			if line.LnumNew == newLine {
   126  				return oldPath, line.LnumOld
   127  			}
   128  		}
   129  	}
   130  	return oldPath, newLine + delta
   131  }