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