github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/fsutils/linecache.go (about) 1 package fsutils 2 3 import ( 4 "bytes" 5 "fmt" 6 "sync" 7 8 "github.com/pkg/errors" 9 ) 10 11 type fileLinesCache [][]byte 12 13 type LineCache struct { 14 files sync.Map 15 fileCache *FileCache 16 } 17 18 func NewLineCache(fc *FileCache) *LineCache { 19 return &LineCache{ 20 fileCache: fc, 21 } 22 } 23 24 // GetLine returns a index1-th (1-based index) line from the file on filePath 25 func (lc *LineCache) GetLine(filePath string, index1 int) (string, error) { 26 if index1 == 0 { // some linters, e.g. gosec can do it: it really means first line 27 index1 = 1 28 } 29 30 const index1To0Offset = -1 31 rawLine, err := lc.getRawLine(filePath, index1+index1To0Offset) 32 if err != nil { 33 return "", err 34 } 35 36 return string(bytes.Trim(rawLine, "\r")), nil 37 } 38 39 func (lc *LineCache) getRawLine(filePath string, index0 int) ([]byte, error) { 40 fc, err := lc.getFileCache(filePath) 41 if err != nil { 42 return nil, errors.Wrapf(err, "failed to get file %s lines cache", filePath) 43 } 44 45 if index0 < 0 { 46 return nil, fmt.Errorf("invalid file line index0 < 0: %d", index0) 47 } 48 49 if index0 >= len(fc) { 50 return nil, fmt.Errorf("invalid file line index0 (%d) >= len(fc) (%d)", index0, len(fc)) 51 } 52 53 return fc[index0], nil 54 } 55 56 func (lc *LineCache) getFileCache(filePath string) (fileLinesCache, error) { 57 loadedFc, ok := lc.files.Load(filePath) 58 if ok { 59 return loadedFc.(fileLinesCache), nil 60 } 61 62 fileBytes, err := lc.fileCache.GetFileBytes(filePath) 63 if err != nil { 64 return nil, errors.Wrapf(err, "can't get file %s bytes from cache", filePath) 65 } 66 67 fc := bytes.Split(fileBytes, []byte("\n")) 68 lc.files.Store(filePath, fileLinesCache(fc)) 69 return fc, nil 70 }