github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/golinters/lll.go (about) 1 package golinters 2 3 import ( 4 "bufio" 5 "fmt" 6 "go/token" 7 "os" 8 "strings" 9 "sync" 10 "unicode/utf8" 11 12 "golang.org/x/tools/go/analysis" 13 14 "github.com/elek/golangci-lint/pkg/golinters/goanalysis" 15 "github.com/elek/golangci-lint/pkg/lint/linter" 16 "github.com/elek/golangci-lint/pkg/result" 17 ) 18 19 func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]result.Issue, error) { 20 var res []result.Issue 21 22 f, err := os.Open(filename) 23 if err != nil { 24 return nil, fmt.Errorf("can't open file %s: %s", filename, err) 25 } 26 defer f.Close() 27 28 lineNumber := 1 29 scanner := bufio.NewScanner(f) 30 for scanner.Scan() { 31 line := scanner.Text() 32 line = strings.Replace(line, "\t", tabSpaces, -1) 33 lineLen := utf8.RuneCountInString(line) 34 if lineLen > maxLineLen { 35 res = append(res, result.Issue{ 36 Pos: token.Position{ 37 Filename: filename, 38 Line: lineNumber, 39 }, 40 Text: fmt.Sprintf("line is %d characters", lineLen), 41 FromLinter: lllName, 42 }) 43 } 44 lineNumber++ 45 } 46 47 if err := scanner.Err(); err != nil { 48 if err == bufio.ErrTooLong && maxLineLen < bufio.MaxScanTokenSize { 49 // scanner.Scan() might fail if the line is longer than bufio.MaxScanTokenSize 50 // In the case where the specified maxLineLen is smaller than bufio.MaxScanTokenSize 51 // we can return this line as a long line instead of returning an error. 52 // The reason for this change is that this case might happen with autogenerated files 53 // The go-bindata tool for instance might generate a file with a very long line. 54 // In this case, as it's a auto generated file, the warning returned by lll will 55 // be ignored. 56 // But if we return a linter error here, and this error happens for an autogenerated 57 // file the error will be discarded (fine), but all the subsequent errors for lll will 58 // be discarded for other files and we'll miss legit error. 59 res = append(res, result.Issue{ 60 Pos: token.Position{ 61 Filename: filename, 62 Line: lineNumber, 63 Column: 1, 64 }, 65 Text: fmt.Sprintf("line is more than %d characters", bufio.MaxScanTokenSize), 66 FromLinter: lllName, 67 }) 68 } else { 69 return nil, fmt.Errorf("can't scan file %s: %s", filename, err) 70 } 71 } 72 73 return res, nil 74 } 75 76 const lllName = "lll" 77 78 func NewLLL() *goanalysis.Linter { 79 var mu sync.Mutex 80 var resIssues []goanalysis.Issue 81 82 analyzer := &analysis.Analyzer{ 83 Name: lllName, 84 Doc: goanalysis.TheOnlyanalyzerDoc, 85 } 86 return goanalysis.NewLinter( 87 lllName, 88 "Reports long lines", 89 []*analysis.Analyzer{analyzer}, 90 nil, 91 ).WithContextSetter(func(lintCtx *linter.Context) { 92 analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { 93 var fileNames []string 94 for _, f := range pass.Files { 95 pos := pass.Fset.PositionFor(f.Pos(), false) 96 fileNames = append(fileNames, pos.Filename) 97 } 98 99 var res []goanalysis.Issue 100 spaces := strings.Repeat(" ", lintCtx.Settings().Lll.TabWidth) 101 for _, f := range fileNames { 102 issues, err := getLLLIssuesForFile(f, lintCtx.Settings().Lll.LineLength, spaces) 103 if err != nil { 104 return nil, err 105 } 106 for i := range issues { 107 res = append(res, goanalysis.NewIssue(&issues[i], pass)) 108 } 109 } 110 111 if len(res) == 0 { 112 return nil, nil 113 } 114 115 mu.Lock() 116 resIssues = append(resIssues, res...) 117 mu.Unlock() 118 119 return nil, nil 120 } 121 }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { 122 return resIssues 123 }).WithLoadMode(goanalysis.LoadModeSyntax) 124 }