github.com/StevenACoffman/golangci-lint@v1.10.1/pkg/result/processors/source_code.go (about)

     1  package processors
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  
     8  	"github.com/golangci/golangci-lint/pkg/logutils"
     9  	"github.com/golangci/golangci-lint/pkg/result"
    10  )
    11  
    12  type linesCache [][]byte
    13  type filesLineCache map[string]linesCache
    14  
    15  type SourceCode struct {
    16  	cache filesLineCache
    17  	log   logutils.Log
    18  }
    19  
    20  var _ Processor = SourceCode{}
    21  
    22  func NewSourceCode(log logutils.Log) *SourceCode {
    23  	return &SourceCode{
    24  		cache: filesLineCache{},
    25  		log:   log,
    26  	}
    27  }
    28  
    29  func (p SourceCode) Name() string {
    30  	return "source_code"
    31  }
    32  
    33  func (p SourceCode) Process(issues []result.Issue) ([]result.Issue, error) {
    34  	return transformIssues(issues, func(i *result.Issue) *result.Issue {
    35  		lines, err := p.getFileLinesForIssue(i)
    36  		if err != nil {
    37  			p.log.Warnf("Failed to get lines for file %s: %s", i.FilePath(), err)
    38  			return i
    39  		}
    40  
    41  		newI := *i
    42  
    43  		lineRange := i.GetLineRange()
    44  		var lineStr string
    45  		for line := lineRange.From; line <= lineRange.To; line++ {
    46  			if line == 0 { // some linters, e.g. gas can do it: it really means first line
    47  				line = 1
    48  			}
    49  
    50  			zeroIndexedLine := line - 1
    51  			if zeroIndexedLine >= len(lines) {
    52  				p.log.Warnf("No line %d in file %s", line, i.FilePath())
    53  				break
    54  			}
    55  
    56  			lineStr = string(bytes.Trim(lines[zeroIndexedLine], "\r"))
    57  			newI.SourceLines = append(newI.SourceLines, lineStr)
    58  		}
    59  
    60  		return &newI
    61  	}), nil
    62  }
    63  
    64  func (p *SourceCode) getFileLinesForIssue(i *result.Issue) (linesCache, error) {
    65  	fc := p.cache[i.FilePath()]
    66  	if fc != nil {
    67  		return fc, nil
    68  	}
    69  
    70  	// TODO: make more optimal algorithm: don't load all files into memory
    71  	fileBytes, err := ioutil.ReadFile(i.FilePath())
    72  	if err != nil {
    73  		return nil, fmt.Errorf("can't read file %s for printing issued line: %s", i.FilePath(), err)
    74  	}
    75  	lines := bytes.Split(fileBytes, []byte("\n")) // TODO: what about \r\n?
    76  	fc = lines
    77  	p.cache[i.FilePath()] = fc
    78  	return fc, nil
    79  }
    80  
    81  func (p SourceCode) Finish() {}