github.com/ldez/golangci-lint@v1.10.1/pkg/result/processors/nolint.go (about) 1 package processors 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/golangci/golangci-lint/pkg/lint/astcache" 10 "github.com/golangci/golangci-lint/pkg/logutils" 11 "github.com/golangci/golangci-lint/pkg/result" 12 ) 13 14 var nolintDebugf = logutils.Debug("nolint") 15 16 type ignoredRange struct { 17 linters []string 18 result.Range 19 col int 20 } 21 22 func (i *ignoredRange) doesMatch(issue *result.Issue) bool { 23 if issue.Line() < i.From || issue.Line() > i.To { 24 return false 25 } 26 27 if len(i.linters) == 0 { 28 return true 29 } 30 31 for _, l := range i.linters { 32 if l == issue.FromLinter { 33 return true 34 } 35 } 36 37 return false 38 } 39 40 type fileData struct { 41 ignoredRanges []ignoredRange 42 } 43 44 type filesCache map[string]*fileData 45 46 type Nolint struct { 47 cache filesCache 48 astCache *astcache.Cache 49 } 50 51 func NewNolint(astCache *astcache.Cache) *Nolint { 52 return &Nolint{ 53 cache: filesCache{}, 54 astCache: astCache, 55 } 56 } 57 58 var _ Processor = &Nolint{} 59 60 func (p Nolint) Name() string { 61 return "nolint" 62 } 63 64 func (p *Nolint) Process(issues []result.Issue) ([]result.Issue, error) { 65 return filterIssuesErr(issues, p.shouldPassIssue) 66 } 67 68 func (p *Nolint) getOrCreateFileData(i *result.Issue) (*fileData, error) { 69 fd := p.cache[i.FilePath()] 70 if fd != nil { 71 return fd, nil 72 } 73 74 fd = &fileData{} 75 p.cache[i.FilePath()] = fd 76 77 file := p.astCache.GetOrParse(i.FilePath()) 78 if file.Err != nil { 79 return nil, fmt.Errorf("can't parse file %s: %s", i.FilePath(), file.Err) 80 } 81 82 fd.ignoredRanges = buildIgnoredRangesForFile(file.F, file.Fset, i.FilePath()) 83 nolintDebugf("file %s: built nolint ranges are %+v", i.FilePath(), fd.ignoredRanges) 84 return fd, nil 85 } 86 87 func buildIgnoredRangesForFile(f *ast.File, fset *token.FileSet, filePath string) []ignoredRange { 88 inlineRanges := extractFileCommentsInlineRanges(fset, f.Comments...) 89 nolintDebugf("file %s: inline nolint ranges are %+v", filePath, inlineRanges) 90 91 if len(inlineRanges) == 0 { 92 return nil 93 } 94 95 e := rangeExpander{ 96 fset: fset, 97 inlineRanges: inlineRanges, 98 } 99 100 ast.Walk(&e, f) 101 102 // TODO: merge all ranges: there are repeated ranges 103 allRanges := append([]ignoredRange{}, inlineRanges...) 104 allRanges = append(allRanges, e.expandedRanges...) 105 106 return allRanges 107 } 108 109 func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) { 110 fd, err := p.getOrCreateFileData(i) 111 if err != nil { 112 return false, err 113 } 114 115 for _, ir := range fd.ignoredRanges { 116 if ir.doesMatch(i) { 117 return false, nil 118 } 119 } 120 121 return true, nil 122 } 123 124 type rangeExpander struct { 125 fset *token.FileSet 126 inlineRanges []ignoredRange 127 expandedRanges []ignoredRange 128 } 129 130 func (e *rangeExpander) Visit(node ast.Node) ast.Visitor { 131 if node == nil { 132 return e 133 } 134 135 nodeStartPos := e.fset.Position(node.Pos()) 136 nodeStartLine := nodeStartPos.Line 137 nodeEndLine := e.fset.Position(node.End()).Line 138 139 var foundRange *ignoredRange 140 for _, r := range e.inlineRanges { 141 if r.To == nodeStartLine-1 && nodeStartPos.Column == r.col { 142 foundRange = &r 143 break 144 } 145 } 146 if foundRange == nil { 147 return e 148 } 149 150 expandedRange := *foundRange 151 if expandedRange.To < nodeEndLine { 152 expandedRange.To = nodeEndLine 153 } 154 nolintDebugf("found range is %v for node %#v [%d;%d], expanded range is %v", 155 *foundRange, node, nodeStartLine, nodeEndLine, expandedRange) 156 e.expandedRanges = append(e.expandedRanges, expandedRange) 157 158 return e 159 } 160 161 func extractFileCommentsInlineRanges(fset *token.FileSet, comments ...*ast.CommentGroup) []ignoredRange { 162 var ret []ignoredRange 163 for _, g := range comments { 164 for _, c := range g.List { 165 text := strings.TrimLeft(c.Text, "/ ") 166 if !strings.HasPrefix(text, "nolint") { 167 continue 168 } 169 170 var linters []string 171 if strings.HasPrefix(text, "nolint:") { 172 // ignore specific linters 173 text = strings.Split(text, "//")[0] // allow another comment after this comment 174 linterItems := strings.Split(strings.TrimPrefix(text, "nolint:"), ",") 175 for _, linter := range linterItems { 176 linterName := strings.TrimSpace(linter) // TODO: validate it here 177 linters = append(linters, linterName) 178 } 179 } // else ignore all linters 180 181 pos := fset.Position(g.Pos()) 182 ret = append(ret, ignoredRange{ 183 Range: result.Range{ 184 From: pos.Line, 185 To: fset.Position(g.End()).Line, 186 }, 187 col: pos.Column, 188 linters: linters, 189 }) 190 } 191 } 192 193 return ret 194 } 195 196 func (p Nolint) Finish() {}