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. // Using a bit laxer rules than https://golang.org/s/generatedcode to // match more generated code. func isGenerated(src []byte) bool { sc := bufio.NewScanner(bytes.NewReader(src)) var hdr, ftr bool for sc.Scan() { b := sc.Bytes() if bytes.HasPrefix(b, genHdr) { hdr = true } if bytes.Contains(b, genFtr) { ftr = true } } return hdr && ftr } 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) { if i.FilePath() == "C" { return false, nil } 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 } } } 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") { continue } pos := fset.Position(g.Pos()) if !strings.HasPrefix(text, "nolint:") { // ignore all linters ret = append(ret, comment{ line: pos.Line, }) continue } // ignore specific linters var linters []string text = strings.Split(text, "//")[0] // allow another comment after this comment linterItems := strings.Split(strings.TrimPrefix(text, "nolint:"), ",") for _, linter := range linterItems { linterName := strings.TrimSpace(linter) // TODO: validate it here linters = append(linters, linterName) } ret = append(ret, comment{ linters: linters, line: pos.Line, }) } } return ret } func (p Nolint) Finish() {}