github.com/mattbailey/reviewdog@v0.10.0/filter.go (about)

     1  package reviewdog
     2  
     3  import (
     4  	"path/filepath"
     5  	"strings"
     6  
     7  	"github.com/reviewdog/reviewdog/diff"
     8  )
     9  
    10  // FilterMode represents enumeration of available filter modes
    11  type FilterMode int
    12  
    13  const (
    14  	// FilterModeDiffContext represents filtering by diff context
    15  	FilterModeDiffContext FilterMode = iota
    16  	// FilterModeAdded represents filtering by added diff lines
    17  	FilterModeAdded
    18  )
    19  
    20  // String implements the flag.Value interface
    21  func (mode *FilterMode) String() string {
    22  	names := [...]string{
    23  		"diff_context",
    24  		"added"}
    25  
    26  	if *mode < FilterModeDiffContext || *mode > FilterModeAdded {
    27  		return "Unknown"
    28  	}
    29  
    30  	return names[*mode]
    31  }
    32  
    33  // Set implements the flag.Value interface
    34  func (mode *FilterMode) Set(value string) error {
    35  	switch value {
    36  	case "diff_context":
    37  		*mode = FilterModeDiffContext
    38  	case "added":
    39  		*mode = FilterModeAdded
    40  	default:
    41  		*mode = FilterModeDiffContext
    42  	}
    43  	return nil
    44  }
    45  
    46  // FilteredCheck represents CheckResult with filtering info.
    47  type FilteredCheck struct {
    48  	*CheckResult
    49  	InDiff   bool
    50  	LnumDiff int
    51  }
    52  
    53  // FilterCheck filters check results by diff. It doesn't drop check which
    54  // is not in diff but set FilteredCheck.InDiff field false.
    55  func FilterCheck(results []*CheckResult, diff []*diff.FileDiff, strip int, wd string, filterMode FilterMode) []*FilteredCheck {
    56  	checks := make([]*FilteredCheck, 0, len(results))
    57  
    58  	var filterFn lineComparator
    59  
    60  	switch filterMode {
    61  	case FilterModeDiffContext:
    62  		filterFn = anyLine
    63  	case FilterModeAdded:
    64  		filterFn = isAddedLine
    65  	}
    66  
    67  	significantlines := significantDiffLines(diff, filterFn, strip)
    68  
    69  	for _, result := range results {
    70  		check := &FilteredCheck{CheckResult: result}
    71  
    72  		significantline := significantlines.Get(result.Path, result.Lnum)
    73  		result.Path = CleanPath(result.Path, wd)
    74  		if significantline != nil {
    75  			check.InDiff = true
    76  			check.LnumDiff = significantline.LnumDiff
    77  		}
    78  
    79  		checks = append(checks, check)
    80  	}
    81  
    82  	return checks
    83  }
    84  
    85  // CleanPath clean up given path. If workdir is not empty, it returns relative
    86  // path to the given workdir.
    87  func CleanPath(path, workdir string) string {
    88  	p := path
    89  	if filepath.IsAbs(path) && workdir != "" {
    90  		relPath, err := filepath.Rel(workdir, path)
    91  		if err == nil {
    92  			p = relPath
    93  		}
    94  	}
    95  	p = filepath.Clean(p)
    96  	if p == "." {
    97  		return ""
    98  	}
    99  	return filepath.ToSlash(p)
   100  }
   101  
   102  // significantLine represents the line in diff we want to filter check results by.
   103  type significantLine struct {
   104  	Path     string // path to new file
   105  	Lnum     int    // the line number in the new file
   106  	LnumDiff int    // the line number of the diff (Same as Lnumdiff of diff.Line)
   107  	Content  string // line content
   108  }
   109  
   110  // posToSignificantLine is a hash table of normalized path to line number to significantLine.
   111  type posToSignificantLine map[string]map[int]*significantLine
   112  
   113  func (p posToSignificantLine) Get(path string, lnum int) *significantLine {
   114  	npath, err := normalizePath(path)
   115  	if err != nil {
   116  		return nil
   117  	}
   118  	ltodiff, ok := p[npath]
   119  	if !ok {
   120  		return nil
   121  	}
   122  	diffline, ok := ltodiff[lnum]
   123  	if !ok {
   124  		return nil
   125  	}
   126  	return diffline
   127  }
   128  
   129  type lineComparator func(diff.Line) bool
   130  
   131  func isAddedLine(line diff.Line) bool {
   132  	return line.Type == diff.LineAdded
   133  }
   134  
   135  func anyLine(line diff.Line) bool {
   136  	return true
   137  }
   138  
   139  // significantDiffLines traverse []*diff.FileDiff and returns posToSignificantLine.
   140  func significantDiffLines(filediffs []*diff.FileDiff, isSignificantLine lineComparator, strip int) posToSignificantLine {
   141  	r := make(posToSignificantLine)
   142  	for _, filediff := range filediffs {
   143  		path := filediff.PathNew
   144  		ltodiff := make(map[int]*significantLine)
   145  		if strip > 0 {
   146  			ps := strings.Split(filepath.ToSlash(filediff.PathNew), "/")
   147  			if len(ps) > strip {
   148  				path = filepath.Join(ps[strip:]...)
   149  			}
   150  		}
   151  		np, err := normalizePath(path)
   152  		if err != nil {
   153  			// FIXME(haya14busa): log or return error?
   154  			continue
   155  		}
   156  		path = np
   157  
   158  		for _, hunk := range filediff.Hunks {
   159  			for _, line := range hunk.Lines {
   160  				if isSignificantLine(*line) {
   161  					ltodiff[line.LnumNew] = &significantLine{
   162  						Path:     path,
   163  						Lnum:     line.LnumNew,
   164  						LnumDiff: line.LnumDiff,
   165  						Content:  line.Content,
   166  					}
   167  				}
   168  			}
   169  		}
   170  		r[path] = ltodiff
   171  	}
   172  	return r
   173  }
   174  
   175  func normalizePath(p string) (string, error) {
   176  	if !filepath.IsAbs(p) {
   177  		path, err := filepath.Abs(p)
   178  		if err != nil {
   179  			return "", err
   180  		}
   181  		p = path
   182  	}
   183  	return filepath.ToSlash(p), nil
   184  }