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() {}