141 lines
3.0 KiB
Go

package processors
import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"strings"
"github.com/golangci/golangci-lint/pkg/result"
)
type comment struct {
linters []string
line int
}
type fileComments []comment
type fileData struct {
comments fileComments
isGenerated bool
}
type filesCache map[string]*fileData
type Nolint struct {
fset *token.FileSet
cache filesCache
}
func NewNolint(fset *token.FileSet) *Nolint {
return &Nolint{
fset: fset,
cache: filesCache{},
}
}
var _ Processor = &Nolint{}
func (p Nolint) Name() string {
return "nolint"
}
func (p *Nolint) Process(issues []result.Issue) ([]result.Issue, error) {
return filterIssuesErr(issues, p.shouldPassIssue)
}
var (
genHdr = []byte("// Code generated ")
genFtr = []byte(" DO NOT EDIT.")
)
// isGenerated reports whether the source file is generated code
// according the rules from https://golang.org/s/generatedcode.
func isGenerated(src []byte) bool {
sc := bufio.NewScanner(bytes.NewReader(src))
for sc.Scan() {
b := sc.Bytes()
if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) {
return true
}
}
return false
}
func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
fd := p.cache[i.FilePath()]
if fd == nil {
fd = &fileData{}
p.cache[i.FilePath()] = fd
src, err := ioutil.ReadFile(i.FilePath())
if err != nil {
return false, fmt.Errorf("can't read file %s: %s", i.FilePath(), err)
}
fd.isGenerated = isGenerated(src)
if fd.isGenerated { // don't report issues for autogenerated files
return false, nil
}
file, err := parser.ParseFile(p.fset, i.FilePath(), src, parser.ParseComments)
if err != nil {
return false, fmt.Errorf("can't parse file %s", i.FilePath())
}
fd.comments = extractFileComments(p.fset, file.Comments...)
}
if fd.isGenerated { // don't report issues for autogenerated files
return false, nil
}
for _, comment := range fd.comments {
if comment.line != i.Line() {
continue
}
if len(comment.linters) == 0 {
return false, nil // skip all linters
}
for _, linter := range comment.linters {
if i.FromLinter == linter {
return false, nil
}
// TODO: check linter name
}
}
return true, nil
}
func extractFileComments(fset *token.FileSet, comments ...*ast.CommentGroup) fileComments {
ret := fileComments{}
for _, g := range comments {
for _, c := range g.List {
text := strings.TrimLeft(c.Text, "/ ")
if strings.HasPrefix(text, "nolint") {
var linters []string
if strings.HasPrefix(text, "nolint:") {
text = strings.Split(text, " ")[0] // allow arbitrary text after this comment
for _, linter := range strings.Split(strings.TrimPrefix(text, "nolint:"), ",") {
linters = append(linters, strings.TrimSpace(linter))
}
}
pos := fset.Position(g.Pos())
ret = append(ret, comment{
linters: linters,
line: pos.Line,
})
}
}
}
return ret
}
func (p Nolint) Finish() {}