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