151 lines
3.1 KiB
Go
151 lines
3.1 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) getOrCreateFileData(i *result.Issue) (*fileData, error) {
|
|
fd := p.cache[i.FilePath()]
|
|
if fd != nil {
|
|
return fd, nil
|
|
}
|
|
|
|
fd = &fileData{}
|
|
p.cache[i.FilePath()] = fd
|
|
|
|
src, err := ioutil.ReadFile(i.FilePath())
|
|
if err != nil {
|
|
return nil, 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 fd, nil
|
|
}
|
|
|
|
file, err := parser.ParseFile(p.fset, i.FilePath(), src, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't parse file %s", i.FilePath())
|
|
}
|
|
|
|
fd.comments = extractFileComments(p.fset, file.Comments...)
|
|
return fd, nil
|
|
}
|
|
|
|
func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
|
|
fd, err := p.getOrCreateFileData(i)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
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() {}
|