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  }