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 }