Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.0.1-2020.1.5 / lint / lint.go
1 // Package lint provides the foundation for tools like staticcheck
2 package lint // import "honnef.co/go/tools/lint"
3
4 import (
5         "bytes"
6         "encoding/gob"
7         "fmt"
8         "go/scanner"
9         "go/token"
10         "go/types"
11         "path/filepath"
12         "sort"
13         "strings"
14         "sync"
15         "sync/atomic"
16         "unicode"
17
18         "golang.org/x/tools/go/analysis"
19         "golang.org/x/tools/go/packages"
20         "honnef.co/go/tools/config"
21         "honnef.co/go/tools/internal/cache"
22 )
23
24 type Documentation struct {
25         Title      string
26         Text       string
27         Since      string
28         NonDefault bool
29         Options    []string
30 }
31
32 func (doc *Documentation) String() string {
33         b := &strings.Builder{}
34         fmt.Fprintf(b, "%s\n\n", doc.Title)
35         if doc.Text != "" {
36                 fmt.Fprintf(b, "%s\n\n", doc.Text)
37         }
38         fmt.Fprint(b, "Available since\n    ")
39         if doc.Since == "" {
40                 fmt.Fprint(b, "unreleased")
41         } else {
42                 fmt.Fprintf(b, "%s", doc.Since)
43         }
44         if doc.NonDefault {
45                 fmt.Fprint(b, ", non-default")
46         }
47         fmt.Fprint(b, "\n")
48         if len(doc.Options) > 0 {
49                 fmt.Fprintf(b, "\nOptions\n")
50                 for _, opt := range doc.Options {
51                         fmt.Fprintf(b, "    %s", opt)
52                 }
53                 fmt.Fprint(b, "\n")
54         }
55         return b.String()
56 }
57
58 type Ignore interface {
59         Match(p Problem) bool
60 }
61
62 type LineIgnore struct {
63         File    string
64         Line    int
65         Checks  []string
66         Matched bool
67         Pos     token.Position
68 }
69
70 func (li *LineIgnore) Match(p Problem) bool {
71         pos := p.Pos
72         if pos.Filename != li.File || pos.Line != li.Line {
73                 return false
74         }
75         for _, c := range li.Checks {
76                 if m, _ := filepath.Match(c, p.Check); m {
77                         li.Matched = true
78                         return true
79                 }
80         }
81         return false
82 }
83
84 func (li *LineIgnore) String() string {
85         matched := "not matched"
86         if li.Matched {
87                 matched = "matched"
88         }
89         return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
90 }
91
92 type FileIgnore struct {
93         File   string
94         Checks []string
95 }
96
97 func (fi *FileIgnore) Match(p Problem) bool {
98         if p.Pos.Filename != fi.File {
99                 return false
100         }
101         for _, c := range fi.Checks {
102                 if m, _ := filepath.Match(c, p.Check); m {
103                         return true
104                 }
105         }
106         return false
107 }
108
109 type Severity uint8
110
111 const (
112         Error Severity = iota
113         Warning
114         Ignored
115 )
116
117 // Problem represents a problem in some source code.
118 type Problem struct {
119         Pos      token.Position
120         End      token.Position
121         Message  string
122         Check    string
123         Severity Severity
124         Related  []Related
125 }
126
127 type Related struct {
128         Pos     token.Position
129         End     token.Position
130         Message string
131 }
132
133 func (p Problem) Equal(o Problem) bool {
134         return p.Pos == o.Pos &&
135                 p.End == o.End &&
136                 p.Message == o.Message &&
137                 p.Check == o.Check &&
138                 p.Severity == o.Severity
139 }
140
141 func (p *Problem) String() string {
142         return fmt.Sprintf("%s (%s)", p.Message, p.Check)
143 }
144
145 // A Linter lints Go source code.
146 type Linter struct {
147         Checkers           []*analysis.Analyzer
148         CumulativeCheckers []CumulativeChecker
149         GoVersion          int
150         Config             config.Config
151         Stats              Stats
152         RepeatAnalyzers    uint
153 }
154
155 type CumulativeChecker interface {
156         Analyzer() *analysis.Analyzer
157         Result() []types.Object
158         ProblemObject(*token.FileSet, types.Object) Problem
159 }
160
161 func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error) {
162         var allAnalyzers []*analysis.Analyzer
163         allAnalyzers = append(allAnalyzers, l.Checkers...)
164         for _, cum := range l.CumulativeCheckers {
165                 allAnalyzers = append(allAnalyzers, cum.Analyzer())
166         }
167
168         // The -checks command line flag overrules all configuration
169         // files, which means that for `-checks="foo"`, no check other
170         // than foo can ever be reported to the user. Make use of this
171         // fact to cull the list of analyses we need to run.
172
173         // replace "inherit" with "all", as we don't want to base the
174         // list of all checks on the default configuration, which
175         // disables certain checks.
176         checks := make([]string, len(l.Config.Checks))
177         copy(checks, l.Config.Checks)
178         for i, c := range checks {
179                 if c == "inherit" {
180                         checks[i] = "all"
181                 }
182         }
183
184         allowed := FilterChecks(allAnalyzers, checks)
185         var allowedAnalyzers []*analysis.Analyzer
186         for _, c := range l.Checkers {
187                 if allowed[c.Name] {
188                         allowedAnalyzers = append(allowedAnalyzers, c)
189                 }
190         }
191         hasCumulative := false
192         for _, cum := range l.CumulativeCheckers {
193                 a := cum.Analyzer()
194                 if allowed[a.Name] {
195                         hasCumulative = true
196                         allowedAnalyzers = append(allowedAnalyzers, a)
197                 }
198         }
199
200         r, err := NewRunner(&l.Stats)
201         if err != nil {
202                 return nil, err
203         }
204         r.goVersion = l.GoVersion
205         r.repeatAnalyzers = l.RepeatAnalyzers
206
207         pkgs, err := r.Run(cfg, patterns, allowedAnalyzers, hasCumulative)
208         if err != nil {
209                 return nil, err
210         }
211
212         tpkgToPkg := map[*types.Package]*Package{}
213         for _, pkg := range pkgs {
214                 tpkgToPkg[pkg.Types] = pkg
215
216                 for _, e := range pkg.errs {
217                         switch e := e.(type) {
218                         case types.Error:
219                                 p := Problem{
220                                         Pos:      e.Fset.PositionFor(e.Pos, false),
221                                         Message:  e.Msg,
222                                         Severity: Error,
223                                         Check:    "compile",
224                                 }
225                                 pkg.problems = append(pkg.problems, p)
226                         case packages.Error:
227                                 msg := e.Msg
228                                 if len(msg) != 0 && msg[0] == '\n' {
229                                         // TODO(dh): See https://github.com/golang/go/issues/32363
230                                         msg = msg[1:]
231                                 }
232
233                                 var pos token.Position
234                                 if e.Pos == "" {
235                                         // Under certain conditions (malformed package
236                                         // declarations, multiple packages in the same
237                                         // directory), go list emits an error on stderr
238                                         // instead of JSON. Those errors do not have
239                                         // associated position information in
240                                         // go/packages.Error, even though the output on
241                                         // stderr may contain it.
242                                         if p, n, err := parsePos(msg); err == nil {
243                                                 if abs, err := filepath.Abs(p.Filename); err == nil {
244                                                         p.Filename = abs
245                                                 }
246                                                 pos = p
247                                                 msg = msg[n+2:]
248                                         }
249                                 } else {
250                                         var err error
251                                         pos, _, err = parsePos(e.Pos)
252                                         if err != nil {
253                                                 panic(fmt.Sprintf("internal error: %s", e))
254                                         }
255                                 }
256                                 p := Problem{
257                                         Pos:      pos,
258                                         Message:  msg,
259                                         Severity: Error,
260                                         Check:    "compile",
261                                 }
262                                 pkg.problems = append(pkg.problems, p)
263                         case scanner.ErrorList:
264                                 for _, e := range e {
265                                         p := Problem{
266                                                 Pos:      e.Pos,
267                                                 Message:  e.Msg,
268                                                 Severity: Error,
269                                                 Check:    "compile",
270                                         }
271                                         pkg.problems = append(pkg.problems, p)
272                                 }
273                         case error:
274                                 p := Problem{
275                                         Pos:      token.Position{},
276                                         Message:  e.Error(),
277                                         Severity: Error,
278                                         Check:    "compile",
279                                 }
280                                 pkg.problems = append(pkg.problems, p)
281                         }
282                 }
283         }
284
285         atomic.StoreUint32(&r.stats.State, StateCumulative)
286         for _, cum := range l.CumulativeCheckers {
287                 for _, res := range cum.Result() {
288                         pkg := tpkgToPkg[res.Pkg()]
289                         if pkg == nil {
290                                 panic(fmt.Sprintf("analyzer %s flagged object %s in package %s, a package that we aren't tracking", cum.Analyzer(), res, res.Pkg()))
291                         }
292                         allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
293                         if allowedChecks[cum.Analyzer().Name] {
294                                 pos := DisplayPosition(pkg.Fset, res.Pos())
295                                 // FIXME(dh): why are we ignoring generated files
296                                 // here? Surely this is specific to 'unused', not all
297                                 // cumulative checkers
298                                 if _, ok := pkg.gen[pos.Filename]; ok {
299                                         continue
300                                 }
301                                 p := cum.ProblemObject(pkg.Fset, res)
302                                 pkg.problems = append(pkg.problems, p)
303                         }
304                 }
305         }
306
307         for _, pkg := range pkgs {
308                 if !pkg.fromSource {
309                         // Don't cache packages that we loaded from the cache
310                         continue
311                 }
312                 cpkg := cachedPackage{
313                         Problems: pkg.problems,
314                         Ignores:  pkg.ignores,
315                         Config:   pkg.cfg,
316                 }
317                 buf := &bytes.Buffer{}
318                 if err := gob.NewEncoder(buf).Encode(cpkg); err != nil {
319                         return nil, err
320                 }
321                 id := cache.Subkey(pkg.actionID, "data "+r.problemsCacheKey)
322                 if err := r.cache.PutBytes(id, buf.Bytes()); err != nil {
323                         return nil, err
324                 }
325         }
326
327         var problems []Problem
328         // Deduplicate line ignores. When U1000 processes a package and
329         // its test variant, it will only emit a single problem for an
330         // unused object, not two problems. We will, however, have two
331         // line ignores, one per package. Without deduplication, one line
332         // ignore will be marked as matched, while the other one won't,
333         // subsequently reporting a "this linter directive didn't match
334         // anything" error.
335         ignores := map[token.Position]Ignore{}
336         for _, pkg := range pkgs {
337                 for _, ig := range pkg.ignores {
338                         if lig, ok := ig.(*LineIgnore); ok {
339                                 ig = ignores[lig.Pos]
340                                 if ig == nil {
341                                         ignores[lig.Pos] = lig
342                                         ig = lig
343                                 }
344                         }
345                         for i := range pkg.problems {
346                                 p := &pkg.problems[i]
347                                 if ig.Match(*p) {
348                                         p.Severity = Ignored
349                                 }
350                         }
351                 }
352
353                 if pkg.cfg == nil {
354                         // The package failed to load, otherwise we would have a
355                         // valid config. Pass through all errors.
356                         problems = append(problems, pkg.problems...)
357                 } else {
358                         for _, p := range pkg.problems {
359                                 allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
360                                 allowedChecks["compile"] = true
361                                 if allowedChecks[p.Check] {
362                                         problems = append(problems, p)
363                                 }
364                         }
365                 }
366
367                 for _, ig := range pkg.ignores {
368                         ig, ok := ig.(*LineIgnore)
369                         if !ok {
370                                 continue
371                         }
372                         ig = ignores[ig.Pos].(*LineIgnore)
373                         if ig.Matched {
374                                 continue
375                         }
376
377                         couldveMatched := false
378                         allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
379                         for _, c := range ig.Checks {
380                                 if !allowedChecks[c] {
381                                         continue
382                                 }
383                                 couldveMatched = true
384                                 break
385                         }
386
387                         if !couldveMatched {
388                                 // The ignored checks were disabled for the containing package.
389                                 // Don't flag the ignore for not having matched.
390                                 continue
391                         }
392                         p := Problem{
393                                 Pos:     ig.Pos,
394                                 Message: "this linter directive didn't match anything; should it be removed?",
395                                 Check:   "",
396                         }
397                         problems = append(problems, p)
398                 }
399         }
400
401         if len(problems) == 0 {
402                 return nil, nil
403         }
404
405         sort.Slice(problems, func(i, j int) bool {
406                 pi := problems[i].Pos
407                 pj := problems[j].Pos
408
409                 if pi.Filename != pj.Filename {
410                         return pi.Filename < pj.Filename
411                 }
412                 if pi.Line != pj.Line {
413                         return pi.Line < pj.Line
414                 }
415                 if pi.Column != pj.Column {
416                         return pi.Column < pj.Column
417                 }
418
419                 return problems[i].Message < problems[j].Message
420         })
421
422         var out []Problem
423         out = append(out, problems[0])
424         for i, p := range problems[1:] {
425                 // We may encounter duplicate problems because one file
426                 // can be part of many packages.
427                 if !problems[i].Equal(p) {
428                         out = append(out, p)
429                 }
430         }
431         return out, nil
432 }
433
434 func FilterChecks(allChecks []*analysis.Analyzer, checks []string) map[string]bool {
435         // OPT(dh): this entire computation could be cached per package
436         allowedChecks := map[string]bool{}
437
438         for _, check := range checks {
439                 b := true
440                 if len(check) > 1 && check[0] == '-' {
441                         b = false
442                         check = check[1:]
443                 }
444                 if check == "*" || check == "all" {
445                         // Match all
446                         for _, c := range allChecks {
447                                 allowedChecks[c.Name] = b
448                         }
449                 } else if strings.HasSuffix(check, "*") {
450                         // Glob
451                         prefix := check[:len(check)-1]
452                         isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
453
454                         for _, c := range allChecks {
455                                 idx := strings.IndexFunc(c.Name, func(r rune) bool { return unicode.IsNumber(r) })
456                                 if isCat {
457                                         // Glob is S*, which should match S1000 but not SA1000
458                                         cat := c.Name[:idx]
459                                         if prefix == cat {
460                                                 allowedChecks[c.Name] = b
461                                         }
462                                 } else {
463                                         // Glob is S1*
464                                         if strings.HasPrefix(c.Name, prefix) {
465                                                 allowedChecks[c.Name] = b
466                                         }
467                                 }
468                         }
469                 } else {
470                         // Literal check name
471                         allowedChecks[check] = b
472                 }
473         }
474         return allowedChecks
475 }
476
477 func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
478         if p == token.NoPos {
479                 return token.Position{}
480         }
481
482         // Only use the adjusted position if it points to another Go file.
483         // This means we'll point to the original file for cgo files, but
484         // we won't point to a YACC grammar file.
485         pos := fset.PositionFor(p, false)
486         adjPos := fset.PositionFor(p, true)
487
488         if filepath.Ext(adjPos.Filename) == ".go" {
489                 return adjPos
490         }
491         return pos
492 }
493
494 var bufferPool = &sync.Pool{
495         New: func() interface{} {
496                 buf := bytes.NewBuffer(nil)
497                 buf.Grow(64)
498                 return buf
499         },
500 }
501
502 func FuncName(f *types.Func) string {
503         buf := bufferPool.Get().(*bytes.Buffer)
504         buf.Reset()
505         if f.Type() != nil {
506                 sig := f.Type().(*types.Signature)
507                 if recv := sig.Recv(); recv != nil {
508                         buf.WriteByte('(')
509                         if _, ok := recv.Type().(*types.Interface); ok {
510                                 // gcimporter creates abstract methods of
511                                 // named interfaces using the interface type
512                                 // (not the named type) as the receiver.
513                                 // Don't print it in full.
514                                 buf.WriteString("interface")
515                         } else {
516                                 types.WriteType(buf, recv.Type(), nil)
517                         }
518                         buf.WriteByte(')')
519                         buf.WriteByte('.')
520                 } else if f.Pkg() != nil {
521                         writePackage(buf, f.Pkg())
522                 }
523         }
524         buf.WriteString(f.Name())
525         s := buf.String()
526         bufferPool.Put(buf)
527         return s
528 }
529
530 func writePackage(buf *bytes.Buffer, pkg *types.Package) {
531         if pkg == nil {
532                 return
533         }
534         s := pkg.Path()
535         if s != "" {
536                 buf.WriteString(s)
537                 buf.WriteByte('.')
538         }
539 }