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