Fix lint errs (#59)
This commit is contained in:
38
vendor/honnef.co/go/tools/lint/generated.go
vendored
Normal file
38
vendor/honnef.co/go/tools/lint/generated.go
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// used by cgo before Go 1.11
|
||||
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
|
||||
prefix = []byte("// Code generated ")
|
||||
suffix = []byte(" DO NOT EDIT.")
|
||||
nl = []byte("\n")
|
||||
crnl = []byte("\r\n")
|
||||
)
|
||||
|
||||
func isGenerated(r io.Reader) bool {
|
||||
br := bufio.NewReader(r)
|
||||
for {
|
||||
s, err := br.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return false
|
||||
}
|
||||
s = bytes.TrimSuffix(s, crnl)
|
||||
s = bytes.TrimSuffix(s, nl)
|
||||
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
|
||||
return true
|
||||
}
|
||||
if bytes.Equal(s, oldCgo) {
|
||||
return true
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
543
vendor/honnef.co/go/tools/lint/lint.go
vendored
543
vendor/honnef.co/go/tools/lint/lint.go
vendored
@ -1,25 +1,22 @@
|
||||
// Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// Package lint provides the foundation for tools like gosimple.
|
||||
// Package lint provides the foundation for tools like staticcheck
|
||||
package lint // import "honnef.co/go/tools/lint"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"honnef.co/go/tools/config"
|
||||
"honnef.co/go/tools/ssa"
|
||||
"honnef.co/go/tools/ssa/ssautil"
|
||||
)
|
||||
@ -28,8 +25,10 @@ type Job struct {
|
||||
Program *Program
|
||||
|
||||
checker string
|
||||
check string
|
||||
check Check
|
||||
problems []Problem
|
||||
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
type Ignore interface {
|
||||
@ -89,7 +88,7 @@ type GlobIgnore struct {
|
||||
|
||||
func (gi *GlobIgnore) Match(p Problem) bool {
|
||||
if gi.Pattern != "*" {
|
||||
pkgpath := p.Package.Path()
|
||||
pkgpath := p.Package.Types.Path()
|
||||
if strings.HasSuffix(pkgpath, "_test") {
|
||||
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
|
||||
}
|
||||
@ -107,31 +106,44 @@ func (gi *GlobIgnore) Match(p Problem) bool {
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
SSA *ssa.Program
|
||||
Prog *loader.Program
|
||||
// TODO(dh): Rename to InitialPackages?
|
||||
Packages []*Pkg
|
||||
SSA *ssa.Program
|
||||
InitialPackages []*Pkg
|
||||
InitialFunctions []*ssa.Function
|
||||
AllPackages []*packages.Package
|
||||
AllFunctions []*ssa.Function
|
||||
Files []*ast.File
|
||||
Info *types.Info
|
||||
GoVersion int
|
||||
|
||||
tokenFileMap map[*token.File]*ast.File
|
||||
astFileMap map[*ast.File]*Pkg
|
||||
packagesMap map[string]*packages.Package
|
||||
|
||||
genMu sync.RWMutex
|
||||
generatedMap map[string]bool
|
||||
}
|
||||
|
||||
func (prog *Program) Fset() *token.FileSet {
|
||||
return prog.InitialPackages[0].Fset
|
||||
}
|
||||
|
||||
type Func func(*Job)
|
||||
|
||||
type Severity uint8
|
||||
|
||||
const (
|
||||
Error Severity = iota
|
||||
Warning
|
||||
Ignored
|
||||
)
|
||||
|
||||
// Problem represents a problem in some source code.
|
||||
type Problem struct {
|
||||
pos token.Pos
|
||||
Position token.Position // position in source file
|
||||
Text string // the prose that describes the problem
|
||||
Check string
|
||||
Checker string
|
||||
Package *types.Package
|
||||
Ignored bool
|
||||
Package *Pkg
|
||||
Severity Severity
|
||||
}
|
||||
|
||||
func (p *Problem) String() string {
|
||||
@ -145,15 +157,25 @@ type Checker interface {
|
||||
Name() string
|
||||
Prefix() string
|
||||
Init(*Program)
|
||||
Funcs() map[string]Func
|
||||
Checks() []Check
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
Fn Func
|
||||
ID string
|
||||
FilterGenerated bool
|
||||
}
|
||||
|
||||
// A Linter lints Go source code.
|
||||
type Linter struct {
|
||||
Checker Checker
|
||||
Checkers []Checker
|
||||
Ignores []Ignore
|
||||
GoVersion int
|
||||
ReturnIgnored bool
|
||||
Config config.Config
|
||||
|
||||
MaxConcurrentJobs int
|
||||
PrintStats bool
|
||||
|
||||
automaticIgnores []Ignore
|
||||
}
|
||||
@ -191,36 +213,6 @@ func (j *Job) File(node Positioner) *ast.File {
|
||||
return j.Program.File(node)
|
||||
}
|
||||
|
||||
// TODO(dh): switch to sort.Slice when Go 1.9 lands.
|
||||
type byPosition struct {
|
||||
fset *token.FileSet
|
||||
ps []Problem
|
||||
}
|
||||
|
||||
func (ps byPosition) Len() int {
|
||||
return len(ps.ps)
|
||||
}
|
||||
|
||||
func (ps byPosition) Less(i int, j int) bool {
|
||||
pi, pj := ps.ps[i].Position, ps.ps[j].Position
|
||||
|
||||
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 ps.ps[i].Text < ps.ps[j].Text
|
||||
}
|
||||
|
||||
func (ps byPosition) Swap(i int, j int) {
|
||||
ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i]
|
||||
}
|
||||
|
||||
func parseDirective(s string) (cmd string, args []string) {
|
||||
if !strings.HasPrefix(s, "//lint:") {
|
||||
return "", nil
|
||||
@ -230,79 +222,131 @@ func parseDirective(s string) (cmd string, args []string) {
|
||||
return fields[0], fields[1:]
|
||||
}
|
||||
|
||||
func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
type PerfStats struct {
|
||||
PackageLoading time.Duration
|
||||
SSABuild time.Duration
|
||||
OtherInitWork time.Duration
|
||||
CheckerInits map[string]time.Duration
|
||||
Jobs []JobStat
|
||||
}
|
||||
|
||||
type JobStat struct {
|
||||
Job string
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
func (stats *PerfStats) Print(w io.Writer) {
|
||||
fmt.Fprintln(w, "Package loading:", stats.PackageLoading)
|
||||
fmt.Fprintln(w, "SSA build:", stats.SSABuild)
|
||||
fmt.Fprintln(w, "Other init work:", stats.OtherInitWork)
|
||||
|
||||
fmt.Fprintln(w, "Checker inits:")
|
||||
for checker, d := range stats.CheckerInits {
|
||||
fmt.Fprintf(w, "\t%s: %s\n", checker, d)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
||||
fmt.Fprintln(w, "Jobs:")
|
||||
sort.Slice(stats.Jobs, func(i, j int) bool {
|
||||
return stats.Jobs[i].Duration < stats.Jobs[j].Duration
|
||||
})
|
||||
var total time.Duration
|
||||
for _, job := range stats.Jobs {
|
||||
fmt.Fprintf(w, "\t%s: %s\n", job.Job, job.Duration)
|
||||
total += job.Duration
|
||||
}
|
||||
fmt.Fprintf(w, "\tTotal: %s\n", total)
|
||||
}
|
||||
|
||||
func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
|
||||
allPkgs := allPackages(initial)
|
||||
t := time.Now()
|
||||
ssaprog, _ := ssautil.Packages(allPkgs, ssa.GlobalDebug)
|
||||
ssaprog.Build()
|
||||
if stats != nil {
|
||||
stats.SSABuild = time.Since(t)
|
||||
}
|
||||
|
||||
t = time.Now()
|
||||
pkgMap := map[*ssa.Package]*Pkg{}
|
||||
var pkgs []*Pkg
|
||||
for _, pkginfo := range lprog.InitialPackages() {
|
||||
ssapkg := ssaprog.Package(pkginfo.Pkg)
|
||||
var bp *build.Package
|
||||
if len(pkginfo.Files) != 0 {
|
||||
path := lprog.Fset.Position(pkginfo.Files[0].Pos()).Filename
|
||||
for _, pkg := range initial {
|
||||
ssapkg := ssaprog.Package(pkg.Types)
|
||||
var cfg config.Config
|
||||
if len(pkg.GoFiles) != 0 {
|
||||
path := pkg.GoFiles[0]
|
||||
dir := filepath.Dir(path)
|
||||
var err error
|
||||
ctx := conf.Build
|
||||
if ctx == nil {
|
||||
ctx = &build.Default
|
||||
}
|
||||
bp, err = ctx.ImportDir(dir, 0)
|
||||
// OPT(dh): we're rebuilding the entire config tree for
|
||||
// each package. for example, if we check a/b/c and
|
||||
// a/b/c/d, we'll process a, a/b, a/b/c, a, a/b, a/b/c,
|
||||
// a/b/c/d – we should cache configs per package and only
|
||||
// load the new levels.
|
||||
cfg, err = config.Load(dir)
|
||||
if err != nil {
|
||||
// shouldn't happen
|
||||
// FIXME(dh): we couldn't load the config, what are we
|
||||
// supposed to do? probably tell the user somehow
|
||||
}
|
||||
cfg = cfg.Merge(l.Config)
|
||||
}
|
||||
|
||||
pkg := &Pkg{
|
||||
Package: ssapkg,
|
||||
Info: pkginfo,
|
||||
BuildPkg: bp,
|
||||
SSA: ssapkg,
|
||||
Package: pkg,
|
||||
Config: cfg,
|
||||
}
|
||||
pkgMap[ssapkg] = pkg
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
|
||||
prog := &Program{
|
||||
SSA: ssaprog,
|
||||
Prog: lprog,
|
||||
Packages: pkgs,
|
||||
Info: &types.Info{},
|
||||
GoVersion: l.GoVersion,
|
||||
tokenFileMap: map[*token.File]*ast.File{},
|
||||
astFileMap: map[*ast.File]*Pkg{},
|
||||
SSA: ssaprog,
|
||||
InitialPackages: pkgs,
|
||||
AllPackages: allPkgs,
|
||||
GoVersion: l.GoVersion,
|
||||
tokenFileMap: map[*token.File]*ast.File{},
|
||||
astFileMap: map[*ast.File]*Pkg{},
|
||||
generatedMap: map[string]bool{},
|
||||
}
|
||||
prog.packagesMap = map[string]*packages.Package{}
|
||||
for _, pkg := range allPkgs {
|
||||
prog.packagesMap[pkg.Types.Path()] = pkg
|
||||
}
|
||||
|
||||
initial := map[*types.Package]struct{}{}
|
||||
isInitial := map[*types.Package]struct{}{}
|
||||
for _, pkg := range pkgs {
|
||||
initial[pkg.Info.Pkg] = struct{}{}
|
||||
isInitial[pkg.Types] = struct{}{}
|
||||
}
|
||||
for fn := range ssautil.AllFunctions(ssaprog) {
|
||||
if fn.Pkg == nil {
|
||||
continue
|
||||
}
|
||||
prog.AllFunctions = append(prog.AllFunctions, fn)
|
||||
if _, ok := initial[fn.Pkg.Pkg]; ok {
|
||||
if _, ok := isInitial[fn.Pkg.Pkg]; ok {
|
||||
prog.InitialFunctions = append(prog.InitialFunctions, fn)
|
||||
}
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
prog.Files = append(prog.Files, pkg.Info.Files...)
|
||||
prog.Files = append(prog.Files, pkg.Syntax...)
|
||||
|
||||
ssapkg := ssaprog.Package(pkg.Info.Pkg)
|
||||
for _, f := range pkg.Info.Files {
|
||||
ssapkg := ssaprog.Package(pkg.Types)
|
||||
for _, f := range pkg.Syntax {
|
||||
prog.astFileMap[f] = pkgMap[ssapkg]
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkginfo := range lprog.AllPackages {
|
||||
for _, f := range pkginfo.Files {
|
||||
tf := lprog.Fset.File(f.Pos())
|
||||
for _, pkg := range allPkgs {
|
||||
for _, f := range pkg.Syntax {
|
||||
tf := pkg.Fset.File(f.Pos())
|
||||
prog.tokenFileMap[tf] = f
|
||||
}
|
||||
}
|
||||
|
||||
var out []Problem
|
||||
l.automaticIgnores = nil
|
||||
for _, pkginfo := range lprog.InitialPackages() {
|
||||
for _, f := range pkginfo.Files {
|
||||
cm := ast.NewCommentMap(lprog.Fset, f, f.Comments)
|
||||
for _, pkg := range initial {
|
||||
for _, f := range pkg.Syntax {
|
||||
cm := ast.NewCommentMap(pkg.Fset, f, f.Comments)
|
||||
for node, cgs := range cm {
|
||||
for _, cg := range cgs {
|
||||
for _, c := range cg.List {
|
||||
@ -315,11 +359,10 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
if len(args) < 2 {
|
||||
// FIXME(dh): this causes duplicated warnings when using megacheck
|
||||
p := Problem{
|
||||
pos: c.Pos(),
|
||||
Position: prog.DisplayPosition(c.Pos()),
|
||||
Text: "malformed linter directive; missing the required reason field?",
|
||||
Check: "",
|
||||
Checker: l.Checker.Name(),
|
||||
Checker: "lint",
|
||||
Package: nil,
|
||||
}
|
||||
out = append(out, p)
|
||||
@ -362,75 +405,84 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
scopes int
|
||||
}{}
|
||||
for _, pkg := range pkgs {
|
||||
sizes.types += len(pkg.Info.Info.Types)
|
||||
sizes.defs += len(pkg.Info.Info.Defs)
|
||||
sizes.uses += len(pkg.Info.Info.Uses)
|
||||
sizes.implicits += len(pkg.Info.Info.Implicits)
|
||||
sizes.selections += len(pkg.Info.Info.Selections)
|
||||
sizes.scopes += len(pkg.Info.Info.Scopes)
|
||||
sizes.types += len(pkg.TypesInfo.Types)
|
||||
sizes.defs += len(pkg.TypesInfo.Defs)
|
||||
sizes.uses += len(pkg.TypesInfo.Uses)
|
||||
sizes.implicits += len(pkg.TypesInfo.Implicits)
|
||||
sizes.selections += len(pkg.TypesInfo.Selections)
|
||||
sizes.scopes += len(pkg.TypesInfo.Scopes)
|
||||
}
|
||||
prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types)
|
||||
prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs)
|
||||
prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses)
|
||||
prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits)
|
||||
prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections)
|
||||
prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes)
|
||||
for _, pkg := range pkgs {
|
||||
for k, v := range pkg.Info.Info.Types {
|
||||
prog.Info.Types[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Defs {
|
||||
prog.Info.Defs[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Uses {
|
||||
prog.Info.Uses[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Implicits {
|
||||
prog.Info.Implicits[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Selections {
|
||||
prog.Info.Selections[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Scopes {
|
||||
prog.Info.Scopes[k] = v
|
||||
}
|
||||
}
|
||||
l.Checker.Init(prog)
|
||||
|
||||
funcs := l.Checker.Funcs()
|
||||
var keys []string
|
||||
for k := range funcs {
|
||||
keys = append(keys, k)
|
||||
if stats != nil {
|
||||
stats.OtherInitWork = time.Since(t)
|
||||
}
|
||||
|
||||
for _, checker := range l.Checkers {
|
||||
t := time.Now()
|
||||
checker.Init(prog)
|
||||
if stats != nil {
|
||||
stats.CheckerInits[checker.Name()] = time.Since(t)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var jobs []*Job
|
||||
for _, k := range keys {
|
||||
j := &Job{
|
||||
Program: prog,
|
||||
checker: l.Checker.Name(),
|
||||
check: k,
|
||||
var allChecks []string
|
||||
|
||||
for _, checker := range l.Checkers {
|
||||
checks := checker.Checks()
|
||||
for _, check := range checks {
|
||||
allChecks = append(allChecks, check.ID)
|
||||
j := &Job{
|
||||
Program: prog,
|
||||
checker: checker.Name(),
|
||||
check: check,
|
||||
}
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
|
||||
max := len(jobs)
|
||||
if l.MaxConcurrentJobs > 0 {
|
||||
max = l.MaxConcurrentJobs
|
||||
}
|
||||
|
||||
sem := make(chan struct{}, max)
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, j := range jobs {
|
||||
wg.Add(1)
|
||||
go func(j *Job) {
|
||||
defer wg.Done()
|
||||
fn := funcs[j.check]
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
fn := j.check.Fn
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
t := time.Now()
|
||||
fn(j)
|
||||
j.duration = time.Since(t)
|
||||
}(j)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, j := range jobs {
|
||||
if stats != nil {
|
||||
stats.Jobs = append(stats.Jobs, JobStat{j.check.ID, j.duration})
|
||||
}
|
||||
for _, p := range j.problems {
|
||||
p.Ignored = l.ignore(p)
|
||||
if l.ReturnIgnored || !p.Ignored {
|
||||
allowedChecks := FilterChecks(allChecks, p.Package.Config.Checks)
|
||||
|
||||
if l.ignore(p) {
|
||||
p.Severity = Ignored
|
||||
}
|
||||
// TODO(dh): support globs in check white/blacklist
|
||||
// OPT(dh): this approach doesn't actually disable checks,
|
||||
// it just discards their results. For the moment, that's
|
||||
// fine. None of our checks are super expensive. In the
|
||||
// future, we may want to provide opt-in expensive
|
||||
// analysis, which shouldn't run at all. It may be easiest
|
||||
// to implement this in the individual checks.
|
||||
if (l.ReturnIgnored || p.Severity != Ignored) && allowedChecks[p.Check] {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
@ -444,39 +496,128 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
if ig.matched {
|
||||
continue
|
||||
}
|
||||
for _, c := range ig.Checks {
|
||||
idx := strings.IndexFunc(c, func(r rune) bool {
|
||||
return unicode.IsNumber(r)
|
||||
})
|
||||
if idx == -1 {
|
||||
// malformed check name, backing out
|
||||
|
||||
couldveMatched := false
|
||||
for f, pkg := range prog.astFileMap {
|
||||
if prog.Fset().Position(f.Pos()).Filename != ig.File {
|
||||
continue
|
||||
}
|
||||
if c[:idx] != l.Checker.Prefix() {
|
||||
// not for this checker
|
||||
continue
|
||||
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
|
||||
for _, c := range ig.Checks {
|
||||
if !allowedChecks[c] {
|
||||
continue
|
||||
}
|
||||
couldveMatched = true
|
||||
break
|
||||
}
|
||||
p := Problem{
|
||||
pos: ig.pos,
|
||||
Position: prog.DisplayPosition(ig.pos),
|
||||
Text: "this linter directive didn't match anything; should it be removed?",
|
||||
Check: "",
|
||||
Checker: l.Checker.Name(),
|
||||
Package: nil,
|
||||
}
|
||||
out = append(out, p)
|
||||
break
|
||||
}
|
||||
|
||||
if !couldveMatched {
|
||||
// The ignored checks were disabled for the containing package.
|
||||
// Don't flag the ignore for not having matched.
|
||||
continue
|
||||
}
|
||||
p := Problem{
|
||||
Position: prog.DisplayPosition(ig.pos),
|
||||
Text: "this linter directive didn't match anything; should it be removed?",
|
||||
Check: "",
|
||||
Checker: "lint",
|
||||
Package: nil,
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
|
||||
sort.Sort(byPosition{lprog.Fset, out})
|
||||
return out
|
||||
sort.Slice(out, func(i int, j int) bool {
|
||||
pi, pj := out[i].Position, out[j].Position
|
||||
|
||||
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 out[i].Text < out[j].Text
|
||||
})
|
||||
|
||||
if l.PrintStats && stats != nil {
|
||||
stats.Print(os.Stderr)
|
||||
}
|
||||
|
||||
if len(out) < 2 {
|
||||
return out
|
||||
}
|
||||
|
||||
uniq := make([]Problem, 0, len(out))
|
||||
uniq = append(uniq, out[0])
|
||||
prev := out[0]
|
||||
for _, p := range out[1:] {
|
||||
if prev.Position == p.Position && prev.Text == p.Text {
|
||||
continue
|
||||
}
|
||||
prev = p
|
||||
uniq = append(uniq, p)
|
||||
}
|
||||
|
||||
return uniq
|
||||
}
|
||||
|
||||
func FilterChecks(allChecks []string, 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] = 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, func(r rune) bool { return unicode.IsNumber(r) })
|
||||
if isCat {
|
||||
// Glob is S*, which should match S1000 but not SA1000
|
||||
cat := c[:idx]
|
||||
if prefix == cat {
|
||||
allowedChecks[c] = b
|
||||
}
|
||||
} else {
|
||||
// Glob is S1*
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
allowedChecks[c] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Literal check name
|
||||
allowedChecks[check] = b
|
||||
}
|
||||
}
|
||||
return allowedChecks
|
||||
}
|
||||
|
||||
func (prog *Program) Package(path string) *packages.Package {
|
||||
return prog.packagesMap[path]
|
||||
}
|
||||
|
||||
// Pkg represents a package being linted.
|
||||
type Pkg struct {
|
||||
*ssa.Package
|
||||
Info *loader.PackageInfo
|
||||
BuildPkg *build.Package
|
||||
SSA *ssa.Package
|
||||
*packages.Package
|
||||
Config config.Config
|
||||
}
|
||||
|
||||
type Positioner interface {
|
||||
@ -484,52 +625,61 @@ type Positioner interface {
|
||||
}
|
||||
|
||||
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
|
||||
// The //line compiler directive can be used to change the file
|
||||
// name and line numbers associated with code. This can, for
|
||||
// example, be used by code generation tools. The most prominent
|
||||
// example is 'go tool cgo', which uses //line directives to refer
|
||||
// back to the original source code.
|
||||
//
|
||||
// In the context of our linters, we need to treat these
|
||||
// directives differently depending on context. For cgo files, we
|
||||
// want to honour the directives, so that line numbers are
|
||||
// adjusted correctly. For all other files, we want to ignore the
|
||||
// directives, so that problems are reported at their actual
|
||||
// position and not, for example, a yacc grammar file. This also
|
||||
// affects the ignore mechanism, since it operates on the position
|
||||
// information stored within problems. With this implementation, a
|
||||
// user will ignore foo.go, not foo.y
|
||||
// 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.
|
||||
|
||||
pkg := prog.astFileMap[prog.tokenFileMap[prog.Prog.Fset.File(p)]]
|
||||
bp := pkg.BuildPkg
|
||||
adjPos := prog.Prog.Fset.Position(p)
|
||||
if bp == nil {
|
||||
// couldn't find the package for some reason (deleted? faulty
|
||||
// file system?)
|
||||
pos := prog.Fset().PositionFor(p, false)
|
||||
adjPos := prog.Fset().PositionFor(p, true)
|
||||
|
||||
if filepath.Ext(adjPos.Filename) == ".go" {
|
||||
return adjPos
|
||||
}
|
||||
base := filepath.Base(adjPos.Filename)
|
||||
for _, f := range bp.CgoFiles {
|
||||
if f == base {
|
||||
// this is a cgo file, use the adjusted position
|
||||
return adjPos
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
func (prog *Program) isGenerated(path string) bool {
|
||||
// This function isn't very efficient in terms of lock contention
|
||||
// and lack of parallelism, but it really shouldn't matter.
|
||||
// Projects consists of thousands of files, and have hundreds of
|
||||
// errors. That's not a lot of calls to isGenerated.
|
||||
|
||||
prog.genMu.RLock()
|
||||
if b, ok := prog.generatedMap[path]; ok {
|
||||
prog.genMu.RUnlock()
|
||||
return b
|
||||
}
|
||||
// not a cgo file, ignore //line directives
|
||||
return prog.Prog.Fset.PositionFor(p, false)
|
||||
prog.genMu.RUnlock()
|
||||
prog.genMu.Lock()
|
||||
defer prog.genMu.Unlock()
|
||||
// recheck to avoid doing extra work in case of race
|
||||
if b, ok := prog.generatedMap[path]; ok {
|
||||
return b
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
b := isGenerated(f)
|
||||
prog.generatedMap[path] = b
|
||||
return b
|
||||
}
|
||||
|
||||
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
|
||||
tf := j.Program.SSA.Fset.File(n.Pos())
|
||||
f := j.Program.tokenFileMap[tf]
|
||||
pkg := j.Program.astFileMap[f].Pkg
|
||||
pkg := j.Program.astFileMap[f]
|
||||
|
||||
pos := j.Program.DisplayPosition(n.Pos())
|
||||
if j.Program.isGenerated(pos.Filename) && j.check.FilterGenerated {
|
||||
return nil
|
||||
}
|
||||
problem := Problem{
|
||||
pos: n.Pos(),
|
||||
Position: pos,
|
||||
Text: fmt.Sprintf(format, args...),
|
||||
Check: j.check,
|
||||
Check: j.check.ID,
|
||||
Checker: j.checker,
|
||||
Package: pkg,
|
||||
}
|
||||
@ -541,3 +691,16 @@ func (j *Job) NodePackage(node Positioner) *Pkg {
|
||||
f := j.File(node)
|
||||
return j.Program.astFileMap[f]
|
||||
}
|
||||
|
||||
func allPackages(pkgs []*packages.Package) []*packages.Package {
|
||||
var out []*packages.Package
|
||||
packages.Visit(
|
||||
pkgs,
|
||||
func(pkg *packages.Package) bool {
|
||||
out = append(out, pkg)
|
||||
return true
|
||||
},
|
||||
nil,
|
||||
)
|
||||
return out
|
||||
}
|
||||
|
129
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
129
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
@ -103,10 +103,21 @@ func IsZero(expr ast.Expr) bool {
|
||||
return IsIntLiteral(expr, "0")
|
||||
}
|
||||
|
||||
func TypeOf(j *lint.Job, expr ast.Expr) types.Type { return j.Program.Info.TypeOf(expr) }
|
||||
func TypeOf(j *lint.Job, expr ast.Expr) types.Type {
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
return j.NodePackage(expr).TypesInfo.TypeOf(expr)
|
||||
}
|
||||
|
||||
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
|
||||
|
||||
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object { return j.Program.Info.ObjectOf(ident) }
|
||||
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object {
|
||||
if ident == nil {
|
||||
return nil
|
||||
}
|
||||
return j.NodePackage(ident).TypesInfo.ObjectOf(ident)
|
||||
}
|
||||
|
||||
func IsInTest(j *lint.Job, node lint.Positioner) bool {
|
||||
// FIXME(dh): this doesn't work for global variables with
|
||||
@ -123,14 +134,15 @@ func IsInMain(j *lint.Job, node lint.Positioner) bool {
|
||||
if pkg == nil {
|
||||
return false
|
||||
}
|
||||
return pkg.Pkg.Name() == "main"
|
||||
return pkg.Types.Name() == "main"
|
||||
}
|
||||
|
||||
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||
sel := j.Program.Info.Selections[expr]
|
||||
info := j.NodePackage(expr).TypesInfo
|
||||
sel := info.Selections[expr]
|
||||
if sel == nil {
|
||||
if x, ok := expr.X.(*ast.Ident); ok {
|
||||
pkg, ok := j.Program.Info.ObjectOf(x).(*types.PkgName)
|
||||
pkg, ok := info.ObjectOf(x).(*types.PkgName)
|
||||
if !ok {
|
||||
// This shouldn't happen
|
||||
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
|
||||
@ -143,11 +155,11 @@ func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||
}
|
||||
|
||||
func IsNil(j *lint.Job, expr ast.Expr) bool {
|
||||
return j.Program.Info.Types[expr].IsNil()
|
||||
return j.NodePackage(expr).TypesInfo.Types[expr].IsNil()
|
||||
}
|
||||
|
||||
func BoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||
val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||
val := j.NodePackage(expr).TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||
return constant.BoolVal(val)
|
||||
}
|
||||
|
||||
@ -160,7 +172,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
obj := j.Program.Info.ObjectOf(ident)
|
||||
obj := j.NodePackage(expr).TypesInfo.ObjectOf(ident)
|
||||
c, ok := obj.(*types.Const)
|
||||
if !ok {
|
||||
return false
|
||||
@ -176,7 +188,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||
}
|
||||
|
||||
func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||
tv := j.Program.Info.Types[expr]
|
||||
tv := j.NodePackage(expr).TypesInfo.Types[expr]
|
||||
if tv.Value == nil {
|
||||
return 0, false
|
||||
}
|
||||
@ -187,7 +199,7 @@ func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||
}
|
||||
|
||||
func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
|
||||
val := j.Program.Info.Types[expr].Value
|
||||
val := j.NodePackage(expr).TypesInfo.Types[expr].Value
|
||||
if val == nil {
|
||||
return "", false
|
||||
}
|
||||
@ -220,17 +232,35 @@ func IsGoVersion(j *lint.Job, minor int) bool {
|
||||
return j.Program.GoVersion >= minor
|
||||
}
|
||||
|
||||
func CallNameAST(j *lint.Job, call *ast.CallExpr) string {
|
||||
switch fun := call.Fun.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
fn, ok := ObjectOf(j, fun.Sel).(*types.Func)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fn.FullName()
|
||||
case *ast.Ident:
|
||||
obj := ObjectOf(j, fun)
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
return obj.FullName()
|
||||
case *types.Builtin:
|
||||
return obj.Name()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func IsCallToAST(j *lint.Job, node ast.Node, name string) bool {
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
sel, ok := call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func)
|
||||
return ok && fn.FullName() == name
|
||||
return CallNameAST(j, call) == name
|
||||
}
|
||||
|
||||
func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
|
||||
@ -280,3 +310,70 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) {
|
||||
}
|
||||
ast.Inspect(node, fn)
|
||||
}
|
||||
|
||||
func GroupSpecs(j *lint.Job, specs []ast.Spec) [][]ast.Spec {
|
||||
if len(specs) == 0 {
|
||||
return nil
|
||||
}
|
||||
fset := j.Program.SSA.Fset
|
||||
groups := make([][]ast.Spec, 1)
|
||||
groups[0] = append(groups[0], specs[0])
|
||||
|
||||
for _, spec := range specs[1:] {
|
||||
g := groups[len(groups)-1]
|
||||
if fset.PositionFor(spec.Pos(), false).Line-1 !=
|
||||
fset.PositionFor(g[len(g)-1].End(), false).Line {
|
||||
|
||||
groups = append(groups, nil)
|
||||
}
|
||||
|
||||
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
func IsObject(obj types.Object, name string) bool {
|
||||
var path string
|
||||
if pkg := obj.Pkg(); pkg != nil {
|
||||
path = pkg.Path() + "."
|
||||
}
|
||||
return path+obj.Name() == name
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Var *types.Var
|
||||
Tag string
|
||||
Path []int
|
||||
}
|
||||
|
||||
// FlattenFields recursively flattens T and embedded structs,
|
||||
// returning a list of fields. If multiple fields with the same name
|
||||
// exist, all will be returned.
|
||||
func FlattenFields(T *types.Struct) []Field {
|
||||
return flattenFields(T, nil, nil)
|
||||
}
|
||||
|
||||
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
|
||||
if seen == nil {
|
||||
seen = map[types.Type]bool{}
|
||||
}
|
||||
if seen[T] {
|
||||
return nil
|
||||
}
|
||||
seen[T] = true
|
||||
var out []Field
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
field := T.Field(i)
|
||||
tag := T.Tag(i)
|
||||
np := append(path[:len(path):len(path)], i)
|
||||
if field.Anonymous() {
|
||||
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
|
||||
out = append(out, flattenFields(s, np, seen)...)
|
||||
}
|
||||
} else {
|
||||
out = append(out, Field{field, tag, np})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
128
vendor/honnef.co/go/tools/lint/lintutil/format/format.go
vendored
Normal file
128
vendor/honnef.co/go/tools/lint/lintutil/format/format.go
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
// Package format provides formatters for linter problems.
|
||||
package format
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
"honnef.co/go/tools/lint"
|
||||
)
|
||||
|
||||
func shortPath(path string) string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
|
||||
return rel
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func relativePositionString(pos token.Position) string {
|
||||
s := shortPath(pos.Filename)
|
||||
if pos.IsValid() {
|
||||
if s != "" {
|
||||
s += ":"
|
||||
}
|
||||
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
||||
}
|
||||
if s == "" {
|
||||
s = "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type Statter interface {
|
||||
Stats(total, errors, warnings int)
|
||||
}
|
||||
|
||||
type Formatter interface {
|
||||
Format(p lint.Problem)
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func (o Text) Format(p lint.Problem) {
|
||||
fmt.Fprintf(o.W, "%v: %s\n", relativePositionString(p.Position), p.String())
|
||||
}
|
||||
|
||||
type JSON struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func severity(s lint.Severity) string {
|
||||
switch s {
|
||||
case lint.Error:
|
||||
return "error"
|
||||
case lint.Warning:
|
||||
return "warning"
|
||||
case lint.Ignored:
|
||||
return "ignored"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (o JSON) Format(p lint.Problem) {
|
||||
type location struct {
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
}
|
||||
jp := struct {
|
||||
Code string `json:"code"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Location location `json:"location"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Code: p.Check,
|
||||
Severity: severity(p.Severity),
|
||||
Location: location{
|
||||
File: p.Position.Filename,
|
||||
Line: p.Position.Line,
|
||||
Column: p.Position.Column,
|
||||
},
|
||||
Message: p.Text,
|
||||
}
|
||||
_ = json.NewEncoder(o.W).Encode(jp)
|
||||
}
|
||||
|
||||
type Stylish struct {
|
||||
W io.Writer
|
||||
|
||||
prevFile string
|
||||
tw *tabwriter.Writer
|
||||
}
|
||||
|
||||
func (o *Stylish) Format(p lint.Problem) {
|
||||
if p.Position.Filename == "" {
|
||||
p.Position.Filename = "-"
|
||||
}
|
||||
|
||||
if p.Position.Filename != o.prevFile {
|
||||
if o.prevFile != "" {
|
||||
o.tw.Flush()
|
||||
fmt.Fprintln(o.W)
|
||||
}
|
||||
fmt.Fprintln(o.W, p.Position.Filename)
|
||||
o.prevFile = p.Position.Filename
|
||||
o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0)
|
||||
}
|
||||
fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", p.Position.Line, p.Position.Column, p.Check, p.Text)
|
||||
}
|
||||
|
||||
func (o *Stylish) Stats(total, errors, warnings int) {
|
||||
if o.tw != nil {
|
||||
o.tw.Flush()
|
||||
fmt.Fprintln(o.W)
|
||||
}
|
||||
fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings)\n",
|
||||
total, errors, warnings)
|
||||
}
|
374
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
374
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
@ -8,70 +8,28 @@
|
||||
package lintutil // import "honnef.co/go/tools/lint/lintutil"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"honnef.co/go/tools/config"
|
||||
"honnef.co/go/tools/lint"
|
||||
"honnef.co/go/tools/lint/lintutil/format"
|
||||
"honnef.co/go/tools/version"
|
||||
|
||||
"github.com/kisielk/gotool"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
type OutputFormatter interface {
|
||||
Format(p lint.Problem)
|
||||
}
|
||||
|
||||
type TextOutput struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (o TextOutput) Format(p lint.Problem) {
|
||||
fmt.Fprintf(o.w, "%v: %s\n", relativePositionString(p.Position), p.String())
|
||||
}
|
||||
|
||||
type JSONOutput struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (o JSONOutput) Format(p lint.Problem) {
|
||||
type location struct {
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
}
|
||||
jp := struct {
|
||||
Checker string `json:"checker"`
|
||||
Code string `json:"code"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Location location `json:"location"`
|
||||
Message string `json:"message"`
|
||||
Ignored bool `json:"ignored"`
|
||||
}{
|
||||
p.Checker,
|
||||
p.Check,
|
||||
"", // TODO(dh): support severity
|
||||
location{
|
||||
p.Position.Filename,
|
||||
p.Position.Line,
|
||||
p.Position.Column,
|
||||
},
|
||||
p.Text,
|
||||
p.Ignored,
|
||||
}
|
||||
_ = json.NewEncoder(o.w).Encode(jp)
|
||||
}
|
||||
func usage(name string, flags *flag.FlagSet) func() {
|
||||
return func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
|
||||
@ -84,38 +42,6 @@ func usage(name string, flags *flag.FlagSet) func() {
|
||||
}
|
||||
}
|
||||
|
||||
type runner struct {
|
||||
checker lint.Checker
|
||||
tags []string
|
||||
ignores []lint.Ignore
|
||||
version int
|
||||
returnIgnored bool
|
||||
}
|
||||
|
||||
func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) {
|
||||
if len(importPaths) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if strings.HasSuffix(importPaths[0], ".go") {
|
||||
// User is specifying a package in terms of .go files, don't resolve
|
||||
return true, nil
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ctx := build.Default
|
||||
ctx.BuildTags = tags
|
||||
for i, path := range importPaths {
|
||||
bpkg, err := ctx.Import(path, wd, build.FindOnly)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("can't load package %q: %v", path, err)
|
||||
}
|
||||
importPaths[i] = bpkg.ImportPath
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func parseIgnore(s string) ([]lint.Ignore, error) {
|
||||
var out []lint.Ignore
|
||||
if len(s) == 0 {
|
||||
@ -158,16 +84,41 @@ func (v *versionFlag) Get() interface{} {
|
||||
return int(*v)
|
||||
}
|
||||
|
||||
type list []string
|
||||
|
||||
func (list *list) String() string {
|
||||
return `"` + strings.Join(*list, ",") + `"`
|
||||
}
|
||||
|
||||
func (list *list) Set(s string) error {
|
||||
if s == "" {
|
||||
*list = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
*list = strings.Split(s, ",")
|
||||
return nil
|
||||
}
|
||||
|
||||
func FlagSet(name string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||
flags.Usage = usage(name, flags)
|
||||
flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead")
|
||||
flags.String("tags", "", "List of `build tags`")
|
||||
flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'")
|
||||
flags.String("ignore", "", "Deprecated: use linter directives instead")
|
||||
flags.Bool("tests", true, "Include tests")
|
||||
flags.Bool("version", false, "Print version and exit")
|
||||
flags.Bool("show-ignored", false, "Don't filter ignored problems")
|
||||
flags.String("f", "text", "Output `format` (valid choices are 'text' and 'json')")
|
||||
flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
|
||||
|
||||
flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
|
||||
flags.Bool("debug.print-stats", false, "Print debug statistics")
|
||||
flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
|
||||
flags.String("debug.memprofile", "", "Write memory profile to `file`")
|
||||
|
||||
checks := list{"inherit"}
|
||||
fail := list{"all"}
|
||||
flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
|
||||
flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
|
||||
|
||||
tags := build.Default.ReleaseTags
|
||||
v := tags[len(tags)-1][2:]
|
||||
@ -180,76 +131,129 @@ func FlagSet(name string) *flag.FlagSet {
|
||||
return flags
|
||||
}
|
||||
|
||||
type CheckerConfig struct {
|
||||
Checker lint.Checker
|
||||
ExitNonZero bool
|
||||
}
|
||||
|
||||
func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet) {
|
||||
func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
|
||||
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
|
||||
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
|
||||
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
|
||||
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
|
||||
format := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
||||
formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
||||
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
|
||||
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
|
||||
|
||||
if printVersion {
|
||||
version.Print()
|
||||
os.Exit(0)
|
||||
maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
|
||||
printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
|
||||
cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
|
||||
memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
|
||||
|
||||
cfg := config.Config{}
|
||||
cfg.Checks = *fs.Lookup("checks").Value.(*list)
|
||||
|
||||
exit := func(code int) {
|
||||
if cpuProfile != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
if memProfile != "" {
|
||||
f, err := os.Create(memProfile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.GC()
|
||||
pprof.WriteHeapProfile(f)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
if cpuProfile != "" {
|
||||
f, err := os.Create(cpuProfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
}
|
||||
|
||||
var cs []lint.Checker
|
||||
for _, conf := range confs {
|
||||
cs = append(cs, conf.Checker)
|
||||
if printVersion {
|
||||
version.Print()
|
||||
exit(0)
|
||||
}
|
||||
pss, err := Lint(cs, fs.Args(), &Options{
|
||||
|
||||
ps, err := Lint(cs, fs.Args(), &Options{
|
||||
Tags: strings.Fields(tags),
|
||||
LintTests: tests,
|
||||
Ignores: ignore,
|
||||
GoVersion: goVersion,
|
||||
ReturnIgnored: showIgnored,
|
||||
Config: cfg,
|
||||
|
||||
MaxConcurrentJobs: maxConcurrentJobs,
|
||||
PrintStats: printStats,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
var ps []lint.Problem
|
||||
for _, p := range pss {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
|
||||
var f OutputFormatter
|
||||
switch format {
|
||||
var f format.Formatter
|
||||
switch formatter {
|
||||
case "text":
|
||||
f = TextOutput{os.Stdout}
|
||||
f = format.Text{W: os.Stdout}
|
||||
case "stylish":
|
||||
f = &format.Stylish{W: os.Stdout}
|
||||
case "json":
|
||||
f = JSONOutput{os.Stdout}
|
||||
f = format.JSON{W: os.Stdout}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format)
|
||||
os.Exit(2)
|
||||
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
|
||||
exit(2)
|
||||
}
|
||||
|
||||
var (
|
||||
total int
|
||||
errors int
|
||||
warnings int
|
||||
)
|
||||
|
||||
fail := *fs.Lookup("fail").Value.(*list)
|
||||
var allChecks []string
|
||||
for _, p := range ps {
|
||||
allChecks = append(allChecks, p.Check)
|
||||
}
|
||||
|
||||
shouldExit := lint.FilterChecks(allChecks, fail)
|
||||
|
||||
total = len(ps)
|
||||
for _, p := range ps {
|
||||
if shouldExit[p.Check] {
|
||||
errors++
|
||||
} else {
|
||||
p.Severity = lint.Warning
|
||||
warnings++
|
||||
}
|
||||
f.Format(p)
|
||||
}
|
||||
for i, p := range pss {
|
||||
if len(p) != 0 && confs[i].ExitNonZero {
|
||||
os.Exit(1)
|
||||
}
|
||||
if f, ok := f.(format.Statter); ok {
|
||||
f.Stats(total, errors, warnings)
|
||||
}
|
||||
if errors > 0 {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Config config.Config
|
||||
|
||||
Tags []string
|
||||
LintTests bool
|
||||
Ignores string
|
||||
GoVersion int
|
||||
ReturnIgnored bool
|
||||
|
||||
MaxConcurrentJobs int
|
||||
PrintStats bool
|
||||
}
|
||||
|
||||
func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) {
|
||||
func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, error) {
|
||||
stats := lint.PerfStats{
|
||||
CheckerInits: map[string]time.Duration{},
|
||||
}
|
||||
|
||||
if opt == nil {
|
||||
opt = &Options{}
|
||||
}
|
||||
@ -257,94 +261,102 @@ func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths := gotool.ImportPaths(pkgs)
|
||||
goFiles, err := resolveRelative(paths, opt.Tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := build.Default
|
||||
ctx.BuildTags = opt.Tags
|
||||
hadError := false
|
||||
conf := &loader.Config{
|
||||
Build: &ctx,
|
||||
ParserMode: parser.ParseComments,
|
||||
ImportPkgs: map[string]bool{},
|
||||
TypeChecker: types.Config{
|
||||
Sizes: types.SizesFor(ctx.Compiler, ctx.GOARCH),
|
||||
Error: func(err error) {
|
||||
// Only print the first error found
|
||||
if hadError {
|
||||
return
|
||||
}
|
||||
hadError = true
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
},
|
||||
|
||||
conf := &packages.Config{
|
||||
Mode: packages.LoadAllSyntax,
|
||||
Tests: opt.LintTests,
|
||||
BuildFlags: []string{
|
||||
"-tags=" + strings.Join(opt.Tags, " "),
|
||||
},
|
||||
}
|
||||
if goFiles {
|
||||
conf.CreateFromFilenames("adhoc", paths...)
|
||||
} else {
|
||||
for _, path := range paths {
|
||||
conf.ImportPkgs[path] = opt.LintTests
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
if len(paths) == 0 {
|
||||
paths = []string{"."}
|
||||
}
|
||||
lprog, err := conf.Load()
|
||||
pkgs, err := packages.Load(conf, paths...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PackageLoading = time.Since(t)
|
||||
|
||||
var problems [][]lint.Problem
|
||||
for _, c := range cs {
|
||||
runner := &runner{
|
||||
checker: c,
|
||||
tags: opt.Tags,
|
||||
ignores: ignores,
|
||||
version: opt.GoVersion,
|
||||
returnIgnored: opt.ReturnIgnored,
|
||||
var problems []lint.Problem
|
||||
workingPkgs := make([]*packages.Package, 0, len(pkgs))
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.IllTyped {
|
||||
problems = append(problems, compileErrors(pkg)...)
|
||||
} else {
|
||||
workingPkgs = append(workingPkgs, pkg)
|
||||
}
|
||||
problems = append(problems, runner.lint(lprog, conf))
|
||||
}
|
||||
|
||||
if len(workingPkgs) == 0 {
|
||||
return problems, nil
|
||||
}
|
||||
|
||||
l := &lint.Linter{
|
||||
Checkers: cs,
|
||||
Ignores: ignores,
|
||||
GoVersion: opt.GoVersion,
|
||||
ReturnIgnored: opt.ReturnIgnored,
|
||||
Config: opt.Config,
|
||||
|
||||
MaxConcurrentJobs: opt.MaxConcurrentJobs,
|
||||
PrintStats: opt.PrintStats,
|
||||
}
|
||||
problems = append(problems, l.Lint(workingPkgs, &stats)...)
|
||||
|
||||
return problems, nil
|
||||
}
|
||||
|
||||
func shortPath(path string) string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return path
|
||||
var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
|
||||
|
||||
func parsePos(pos string) token.Position {
|
||||
if pos == "-" || pos == "" {
|
||||
return token.Position{}
|
||||
}
|
||||
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
|
||||
return rel
|
||||
parts := posRe.FindStringSubmatch(pos)
|
||||
if parts == nil {
|
||||
panic(fmt.Sprintf("internal error: malformed position %q", pos))
|
||||
}
|
||||
file := parts[1]
|
||||
line, _ := strconv.Atoi(parts[2])
|
||||
col, _ := strconv.Atoi(parts[3])
|
||||
return token.Position{
|
||||
Filename: file,
|
||||
Line: line,
|
||||
Column: col,
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func relativePositionString(pos token.Position) string {
|
||||
s := shortPath(pos.Filename)
|
||||
if pos.IsValid() {
|
||||
if s != "" {
|
||||
s += ":"
|
||||
func compileErrors(pkg *packages.Package) []lint.Problem {
|
||||
if !pkg.IllTyped {
|
||||
return nil
|
||||
}
|
||||
if len(pkg.Errors) == 0 {
|
||||
// transitively ill-typed
|
||||
var ps []lint.Problem
|
||||
for _, imp := range pkg.Imports {
|
||||
ps = append(ps, compileErrors(imp)...)
|
||||
}
|
||||
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
||||
return ps
|
||||
}
|
||||
if s == "" {
|
||||
s = "-"
|
||||
var ps []lint.Problem
|
||||
for _, err := range pkg.Errors {
|
||||
p := lint.Problem{
|
||||
Position: parsePos(err.Pos),
|
||||
Text: err.Msg,
|
||||
Checker: "compiler",
|
||||
Check: "compile",
|
||||
}
|
||||
ps = append(ps, p)
|
||||
}
|
||||
return s
|
||||
return ps
|
||||
}
|
||||
|
||||
func ProcessArgs(name string, cs []CheckerConfig, args []string) {
|
||||
func ProcessArgs(name string, cs []lint.Checker, args []string) {
|
||||
flags := FlagSet(name)
|
||||
flags.Parse(args)
|
||||
|
||||
ProcessFlagSet(cs, flags)
|
||||
}
|
||||
|
||||
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem {
|
||||
l := &lint.Linter{
|
||||
Checker: runner.checker,
|
||||
Ignores: runner.ignores,
|
||||
GoVersion: runner.version,
|
||||
ReturnIgnored: runner.returnIgnored,
|
||||
}
|
||||
return l.Lint(lprog, conf)
|
||||
}
|
||||
|
Reference in New Issue
Block a user