github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/lint/linter.go (about)

     1  package lint
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"go/token"
     8  	"os"
     9  	"regexp"
    10  	"strconv"
    11  	"sync"
    12  )
    13  
    14  // ReadFile defines an abstraction for reading files.
    15  type ReadFile func(path string) (result []byte, err error)
    16  
    17  type disabledIntervalsMap = map[string][]DisabledInterval
    18  
    19  // Linter is used for linting set of files.
    20  type Linter struct {
    21  	reader         ReadFile
    22  	fileReadTokens chan struct{}
    23  }
    24  
    25  // New creates a new Linter
    26  func New(reader ReadFile, maxOpenFiles int) Linter {
    27  	var fileReadTokens chan struct{}
    28  	if maxOpenFiles > 0 {
    29  		fileReadTokens = make(chan struct{}, maxOpenFiles)
    30  	}
    31  	return Linter{
    32  		reader:         reader,
    33  		fileReadTokens: fileReadTokens,
    34  	}
    35  }
    36  
    37  func (l Linter) readFile(path string) (result []byte, err error) {
    38  	if l.fileReadTokens != nil {
    39  		// "take" a token by writing to the channel.
    40  		// It will block if no more space in the channel's buffer
    41  		l.fileReadTokens <- struct{}{}
    42  		defer func() {
    43  			// "free" a token by reading from the channel
    44  			<-l.fileReadTokens
    45  		}()
    46  	}
    47  
    48  	return l.reader(path)
    49  }
    50  
    51  var (
    52  	genHdr = []byte("// Code generated ")
    53  	genFtr = []byte(" DO NOT EDIT.")
    54  )
    55  
    56  // Lint lints a set of files with the specified rule.
    57  func (l *Linter) Lint(packages [][]string, ruleSet []Rule, config Config) (<-chan Failure, error) {
    58  	failures := make(chan Failure)
    59  
    60  	var wg sync.WaitGroup
    61  	for _, pkg := range packages {
    62  		wg.Add(1)
    63  		go func(pkg []string) {
    64  			if err := l.lintPackage(pkg, ruleSet, config, failures); err != nil {
    65  				fmt.Fprintln(os.Stderr, err)
    66  				os.Exit(1)
    67  			}
    68  			defer wg.Done()
    69  		}(pkg)
    70  	}
    71  
    72  	go func() {
    73  		wg.Wait()
    74  		close(failures)
    75  	}()
    76  
    77  	return failures, nil
    78  }
    79  
    80  func (l *Linter) lintPackage(filenames []string, ruleSet []Rule, config Config, failures chan Failure) error {
    81  	pkg := &Package{
    82  		fset:  token.NewFileSet(),
    83  		files: map[string]*File{},
    84  		mu:    sync.Mutex{},
    85  	}
    86  	for _, filename := range filenames {
    87  		content, err := l.readFile(filename)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		if !config.IgnoreGeneratedHeader && isGenerated(content) {
    92  			continue
    93  		}
    94  
    95  		file, err := NewFile(filename, content, pkg)
    96  		if err != nil {
    97  			addInvalidFileFailure(filename, err.Error(), failures)
    98  			continue
    99  		}
   100  		pkg.files[filename] = file
   101  	}
   102  
   103  	if len(pkg.files) == 0 {
   104  		return nil
   105  	}
   106  
   107  	pkg.lint(ruleSet, config, failures)
   108  
   109  	return nil
   110  }
   111  
   112  // isGenerated reports whether the source file is generated code
   113  // according the rules from https://golang.org/s/generatedcode.
   114  // This is inherited from the original go lint.
   115  func isGenerated(src []byte) bool {
   116  	sc := bufio.NewScanner(bytes.NewReader(src))
   117  	for sc.Scan() {
   118  		b := sc.Bytes()
   119  		if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) {
   120  			return true
   121  		}
   122  	}
   123  	return false
   124  }
   125  
   126  // addInvalidFileFailure adds a failure for an invalid formatted file
   127  func addInvalidFileFailure(filename, errStr string, failures chan Failure) {
   128  	position := getPositionInvalidFile(filename, errStr)
   129  	failures <- Failure{
   130  		Confidence: 1,
   131  		Failure:    fmt.Sprintf("invalid file %s: %v", filename, errStr),
   132  		Category:   "validity",
   133  		Position:   position,
   134  	}
   135  }
   136  
   137  // errPosRegexp matches with an NewFile error message
   138  // i.e. :  corrupted.go:10:4: expected '}', found 'EOF
   139  // first group matches the line and the second group, the column
   140  var errPosRegexp = regexp.MustCompile(".*:(\\d*):(\\d*):.*$")
   141  
   142  // getPositionInvalidFile gets the position of the error in an invalid file
   143  func getPositionInvalidFile(filename, s string) FailurePosition {
   144  	pos := errPosRegexp.FindStringSubmatch(s)
   145  	if len(pos) < 3 {
   146  		return FailurePosition{}
   147  	}
   148  	line, err := strconv.Atoi(pos[1])
   149  	if err != nil {
   150  		return FailurePosition{}
   151  	}
   152  	column, err := strconv.Atoi(pos[2])
   153  	if err != nil {
   154  		return FailurePosition{}
   155  	}
   156  
   157  	return FailurePosition{
   158  		Start: token.Position{
   159  			Filename: filename,
   160  			Line:     line,
   161  			Column:   column,
   162  		},
   163  	}
   164  }