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 }