text: // Package lint provides the foundation for tools like staticcheck
  package lint // import "honnef.co/go/tools/lint"
  
  import (
  	"bytes"
  	"encoding/gob"
  	"fmt"
  	"go/scanner"
  	"go/token"
  	"go/types"
  	"path/filepath"
  	"sort"
  	"strings"
  	"sync"
  	"sync/atomic"
  	"unicode"
  
  	"golang.org/x/tools/go/analysis"
  	"golang.org/x/tools/go/packages"
  	"honnef.co/go/tools/config"
  	"honnef.co/go/tools/internal/cache"
  )
  
  type Documentation struct {
  	Title      string
  	Text       string
  	Since      string
  	NonDefault bool
  	Options    []string
  }
  
  func (doc *Documentation) String() string {
  	b := &strings.Builder{}
  	fmt.Fprintf(b, "%s\n\n", doc.Title)
  	if doc.Text != "" {
  		fmt.Fprintf(b, "%s\n\n", doc.Text)
  	}
  	fmt.Fprint(b, "Available since\n    ")
  	if doc.Since == "" {
  		fmt.Fprint(b, "unreleased")
  	} else {
  		fmt.Fprintf(b, "%s", doc.Since)
  	}
  	if doc.NonDefault {
  		fmt.Fprint(b, ", non-default")
  	}
  	fmt.Fprint(b, "\n")
  	if len(doc.Options) > 0 {
  		fmt.Fprintf(b, "\nOptions\n")
  		for _, opt := range doc.Options {
  			fmt.Fprintf(b, "    %s", opt)
  		}
  		fmt.Fprint(b, "\n")
  	}
  	return b.String()
  }
  
  type Ignore interface {
  	Match(p Problem) bool
  }
  
  type LineIgnore struct {
  	File    string
  	Line    int
  	Checks  []string
  	Matched bool
  	Pos     token.Position
  }
  
  func (li *LineIgnore) Match(p Problem) bool {
  	pos := p.Pos
  	if pos.Filename != li.File || pos.Line != li.Line {
  		return false
  	}
  	for _, c := range li.Checks {
  		if m, _ := filepath.Match(c, p.Check); m {
  			li.Matched = true
  			return true
  		}
  	}
  	return false
  }
  
  func (li *LineIgnore) String() string {
  	matched := "not matched"
  	if li.Matched {
  		matched = "matched"
  	}
  	return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
  }
  
  type FileIgnore struct {
  	File   string
  	Checks []string
  }
  
  func (fi *FileIgnore) Match(p Problem) bool {
  	if p.Pos.Filename != fi.File {
  		return false
  	}
  	for _, c := range fi.Checks {
  		if m, _ := filepath.Match(c, p.Check); m {
  			return true
  		}
  	}
  	return false
  }
  
  type Severity uint8
  
  const (
  	Error Severity = iota
  	Warning
  	Ignored
  )
  
  // Problem represents a problem in some source code.
  type Problem struct {
  	Pos      token.Position
  	End      token.Position
  	Message  string
  	Check    string
  	Severity Severity
  	Related  []Related
  }
  
  type Related struct {
  	Pos     token.Position
  	End     token.Position
  	Message string
  }
  
  func (p Problem) Equal(o Problem) bool {
  	return p.Pos == o.Pos &&
  		p.End == o.End &&
  		p.Message == o.Message &&
  		p.Check == o.Check &&
  		p.Severity == o.Severity
  }
  
  func (p *Problem) String() string {
  	return fmt.Sprintf("%s (%s)", p.Message, p.Check)
  }
  
  // A Linter lints Go source code.
  type Linter struct {
  	Checkers           []*analysis.Analyzer
  	CumulativeCheckers []CumulativeChecker
  	GoVersion          int
  	Config             config.Config
  	Stats              Stats
  	RepeatAnalyzers    uint
  }
  
  type CumulativeChecker interface {
  	Analyzer() *analysis.Analyzer
  	Result() []types.Object
  	ProblemObject(*token.FileSet, types.Object) Problem
  }
  
  func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error) {
  	var allAnalyzers []*analysis.Analyzer
  	allAnalyzers = append(allAnalyzers, l.Checkers...)
  	for _, cum := range l.CumulativeCheckers {
  		allAnalyzers = append(allAnalyzers, cum.Analyzer())
  	}
  
  	// The -checks command line flag overrules all configuration
  	// files, which means that for `-checks="foo"`, no check other
  	// than foo can ever be reported to the user. Make use of this
  	// fact to cull the list of analyses we need to run.
  
  	// replace "inherit" with "all", as we don't want to base the
  	// list of all checks on the default configuration, which
  	// disables certain checks.
  	checks := make([]string, len(l.Config.Checks))
  	copy(checks, l.Config.Checks)
  	for i, c := range checks {
  		if c == "inherit" {
  			checks[i] = "all"
  		}
  	}
  
  	allowed := FilterChecks(allAnalyzers, checks)
  	var allowedAnalyzers []*analysis.Analyzer
  	for _, c := range l.Checkers {
  		if allowed[c.Name] {
  			allowedAnalyzers = append(allowedAnalyzers, c)
  		}
  	}
  	hasCumulative := false
  	for _, cum := range l.CumulativeCheckers {
  		a := cum.Analyzer()
  		if allowed[a.Name] {
  			hasCumulative = true
  			allowedAnalyzers = append(allowedAnalyzers, a)
  		}
  	}
  
  	r, err := NewRunner(&l.Stats)
  	if err != nil {
  		return nil, err
  	}
  	r.goVersion = l.GoVersion
  	r.repeatAnalyzers = l.RepeatAnalyzers
  
  	pkgs, err := r.Run(cfg, patterns, allowedAnalyzers, hasCumulative)
  	if err != nil {
  		return nil, err
  	}
  
  	tpkgToPkg := map[*types.Package]*Package{}
  	for _, pkg := range pkgs {
  		tpkgToPkg[pkg.Types] = pkg
  
  		for _, e := range pkg.errs {
  			switch e := e.(type) {
  			case types.Error:
  				p := Problem{
  					Pos:      e.Fset.PositionFor(e.Pos, false),
  					Message:  e.Msg,
  					Severity: Error,
  					Check:    "compile",
  				}
  				pkg.problems = append(pkg.problems, p)
  			case packages.Error:
  				msg := e.Msg
  				if len(msg) != 0 && msg[0] == '\n' {
  					// TODO(dh): See https://github.com/golang/go/issues/32363
  					msg = msg[1:]
  				}
  
  				var pos token.Position
  				if e.Pos == "" {
  					// Under certain conditions (malformed package
  					// declarations, multiple packages in the same
  					// directory), go list emits an error on stderr
  					// instead of JSON. Those errors do not have
  					// associated position information in
  					// go/packages.Error, even though the output on
  					// stderr may contain it.
  					if p, n, err := parsePos(msg); err == nil {
  						if abs, err := filepath.Abs(p.Filename); err == nil {
  							p.Filename = abs
  						}
  						pos = p
  						msg = msg[n+2:]
  					}
  				} else {
  					var err error
  					pos, _, err = parsePos(e.Pos)
  					if err != nil {
  						panic(fmt.Sprintf("internal error: %s", e))
  					}
  				}
  				p := Problem{
  					Pos:      pos,
  					Message:  msg,
  					Severity: Error,
  					Check:    "compile",
  				}
  				pkg.problems = append(pkg.problems, p)
  			case scanner.ErrorList:
  				for _, e := range e {
  					p := Problem{
  						Pos:      e.Pos,
  						Message:  e.Msg,
  						Severity: Error,
  						Check:    "compile",
  					}
  					pkg.problems = append(pkg.problems, p)
  				}
  			case error:
  				p := Problem{
  					Pos:      token.Position{},
  					Message:  e.Error(),
  					Severity: Error,
  					Check:    "compile",
  				}
  				pkg.problems = append(pkg.problems, p)
  			}
  		}
  	}
  
  	atomic.StoreUint32(&r.stats.State, StateCumulative)
  	for _, cum := range l.CumulativeCheckers {
  		for _, res := range cum.Result() {
  			pkg := tpkgToPkg[res.Pkg()]
  			if pkg == nil {
  				panic(fmt.Sprintf("analyzer %s flagged object %s in package %s, a package that we aren't tracking", cum.Analyzer(), res, res.Pkg()))
  			}
  			allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
  			if allowedChecks[cum.Analyzer().Name] {
  				pos := DisplayPosition(pkg.Fset, res.Pos())
  				// FIXME(dh): why are we ignoring generated files
  				// here? Surely this is specific to 'unused', not all
  				// cumulative checkers
  				if _, ok := pkg.gen[pos.Filename]; ok {
  					continue
  				}
  				p := cum.ProblemObject(pkg.Fset, res)
  				pkg.problems = append(pkg.problems, p)
  			}
  		}
  	}
  
  	for _, pkg := range pkgs {
  		if !pkg.fromSource {
  			// Don't cache packages that we loaded from the cache
  			continue
  		}
  		cpkg := cachedPackage{
  			Problems: pkg.problems,
  			Ignores:  pkg.ignores,
  			Config:   pkg.cfg,
  		}
  		buf := &bytes.Buffer{}
  		if err := gob.NewEncoder(buf).Encode(cpkg); err != nil {
  			return nil, err
  		}
  		id := cache.Subkey(pkg.actionID, "data "+r.problemsCacheKey)
  		if err := r.cache.PutBytes(id, buf.Bytes()); err != nil {
  			return nil, err
  		}
  	}
  
  	var problems []Problem
  	// Deduplicate line ignores. When U1000 processes a package and
  	// its test variant, it will only emit a single problem for an
  	// unused object, not two problems. We will, however, have two
  	// line ignores, one per package. Without deduplication, one line
  	// ignore will be marked as matched, while the other one won't,
  	// subsequently reporting a "this linter directive didn't match
  	// anything" error.
  	ignores := map[token.Position]Ignore{}
  	for _, pkg := range pkgs {
  		for _, ig := range pkg.ignores {
  			if lig, ok := ig.(*LineIgnore); ok {
  				ig = ignores[lig.Pos]
  				if ig == nil {
  					ignores[lig.Pos] = lig
  					ig = lig
  				}
  			}
  			for i := range pkg.problems {
  				p := &pkg.problems[i]
  				if ig.Match(*p) {
  					p.Severity = Ignored
  				}
  			}
  		}
  
  		if pkg.cfg == nil {
  			// The package failed to load, otherwise we would have a
  			// valid config. Pass through all errors.
  			problems = append(problems, pkg.problems...)
  		} else {
  			for _, p := range pkg.problems {
  				allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
  				allowedChecks["compile"] = true
  				if allowedChecks[p.Check] {
  					problems = append(problems, p)
  				}
  			}
  		}
  
  		for _, ig := range pkg.ignores {
  			ig, ok := ig.(*LineIgnore)
  			if !ok {
  				continue
  			}
  			ig = ignores[ig.Pos].(*LineIgnore)
  			if ig.Matched {
  				continue
  			}
  
  			couldveMatched := false
  			allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
  			for _, c := range ig.Checks {
  				if !allowedChecks[c] {
  					continue
  				}
  				couldveMatched = true
  				break
  			}
  
  			if !couldveMatched {
  				// The ignored checks were disabled for the containing package.
  				// Don't flag the ignore for not having matched.
  				continue
  			}
  			p := Problem{
  				Pos:     ig.Pos,
  				Message: "this linter directive didn't match anything; should it be removed?",
  				Check:   "",
  			}
  			problems = append(problems, p)
  		}
  	}
  
  	if len(problems) == 0 {
  		return nil, nil
  	}
  
  	sort.Slice(problems, func(i, j int) bool {
  		pi := problems[i].Pos
  		pj := problems[j].Pos
  
  		if pi.Filename != pj.Filename {
  			return pi.Filename < pj.Filename
  		}
  		if pi.Line != pj.Line {
  			return pi.Line < pj.Line
  		}
  		if pi.Column != pj.Column {
  			return pi.Column < pj.Column
  		}
  
  		return problems[i].Message < problems[j].Message
  	})
  
  	var out []Problem
  	out = append(out, problems[0])
  	for i, p := range problems[1:] {
  		// We may encounter duplicate problems because one file
  		// can be part of many packages.
  		if !problems[i].Equal(p) {
  			out = append(out, p)
  		}
  	}
  	return out, nil
  }
  
  func FilterChecks(allChecks []*analysis.Analyzer, checks []string) map[string]bool {
  	// OPT(dh): this entire computation could be cached per package
  	allowedChecks := map[string]bool{}
  
  	for _, check := range checks {
  		b := true
  		if len(check) > 1 && check[0] == '-' {
  			b = false
  			check = check[1:]
  		}
  		if check == "*" || check == "all" {
  			// Match all
  			for _, c := range allChecks {
  				allowedChecks[c.Name] = b
  			}
  		} else if strings.HasSuffix(check, "*") {
  			// Glob
  			prefix := check[:len(check)-1]
  			isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
  
  			for _, c := range allChecks {
  				idx := strings.IndexFunc(c.Name, func(r rune) bool { return unicode.IsNumber(r) })
  				if isCat {
  					// Glob is S*, which should match S1000 but not SA1000
  					cat := c.Name[:idx]
  					if prefix == cat {
  						allowedChecks[c.Name] = b
  					}
  				} else {
  					// Glob is S1*
  					if strings.HasPrefix(c.Name, prefix) {
  						allowedChecks[c.Name] = b
  					}
  				}
  			}
  		} else {
  			// Literal check name
  			allowedChecks[check] = b
  		}
  	}
  	return allowedChecks
  }
  
  func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
  	if p == token.NoPos {
  		return token.Position{}
  	}
  
  	// Only use the adjusted position if it points to another Go file.
  	// This means we'll point to the original file for cgo files, but
  	// we won't point to a YACC grammar file.
  	pos := fset.PositionFor(p, false)
  	adjPos := fset.PositionFor(p, true)
  
  	if filepath.Ext(adjPos.Filename) == ".go" {
  		return adjPos
  	}
  	return pos
  }
  
  var bufferPool = &sync.Pool{
  	New: func() interface{} {
  		buf := bytes.NewBuffer(nil)
  		buf.Grow(64)
  		return buf
  	},
  }
  
  func FuncName(f *types.Func) string {
  	buf := bufferPool.Get().(*bytes.Buffer)
  	buf.Reset()
  	if f.Type() != nil {
  		sig := f.Type().(*types.Signature)
  		if recv := sig.Recv(); recv != nil {
  			buf.WriteByte('(')
  			if _, ok := recv.Type().(*types.Interface); ok {
  				// gcimporter creates abstract methods of
  				// named interfaces using the interface type
  				// (not the named type) as the receiver.
  				// Don't print it in full.
  				buf.WriteString("interface")
  			} else {
  				types.WriteType(buf, recv.Type(), nil)
  			}
  			buf.WriteByte(')')
  			buf.WriteByte('.')
  		} else if f.Pkg() != nil {
  			writePackage(buf, f.Pkg())
  		}
  	}
  	buf.WriteString(f.Name())
  	s := buf.String()
  	bufferPool.Put(buf)
  	return s
  }
  
  func writePackage(buf *bytes.Buffer, pkg *types.Package) {
  	if pkg == nil {
  		return
  	}
  	s := pkg.Path()
  	if s != "" {
  		buf.WriteString(s)
  		buf.WriteByte('.')
  	}
  }
  