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