diff options
| author | Joseph Richey <joerichey94@gmail.com> | 2018-02-11 20:34:07 -0800 |
|---|---|---|
| committer | Joseph Richey <joerichey94@gmail.com> | 2018-02-11 20:34:07 -0800 |
| commit | 23b8c7b4eab0375b3d59cf4b2a1f3d7356515f95 (patch) | |
| tree | 6ec67f8d6b15420c3870d2b7c43b2b636fbb8349 /vendor/honnef.co/go/tools/lint/lint.go | |
| parent | fff13ea9041a3945e36d5f002c3c0a1e0e93c825 (diff) | |
vendor: include source for tools
This change vendors the source for all our build, formatting, and
linting tools. Generated by running "dep ensure".
Diffstat (limited to 'vendor/honnef.co/go/tools/lint/lint.go')
| -rw-r--r-- | vendor/honnef.co/go/tools/lint/lint.go | 844 |
1 files changed, 844 insertions, 0 deletions
diff --git a/vendor/honnef.co/go/tools/lint/lint.go b/vendor/honnef.co/go/tools/lint/lint.go new file mode 100644 index 0000000..75a5198 --- /dev/null +++ b/vendor/honnef.co/go/tools/lint/lint.go @@ -0,0 +1,844 @@ +// 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 // import "honnef.co/go/tools/lint" + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/constant" + "go/printer" + "go/token" + "go/types" + "path/filepath" + "runtime" + "sort" + "strings" + "sync" + "unicode" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/loader" + "honnef.co/go/tools/ssa" + "honnef.co/go/tools/ssa/ssautil" +) + +type Job struct { + Program *Program + + checker string + check string + problems []Problem +} + +type Ignore interface { + Match(p Problem) bool +} + +type LineIgnore struct { + File string + Line int + Checks []string + matched bool + pos token.Pos +} + +func (li *LineIgnore) Match(p Problem) bool { + if p.Position.Filename != li.File || p.Position.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.Position.Filename != fi.File { + return false + } + for _, c := range fi.Checks { + if m, _ := filepath.Match(c, p.Check); m { + return true + } + } + return false +} + +type GlobIgnore struct { + Pattern string + Checks []string +} + +func (gi *GlobIgnore) Match(p Problem) bool { + if gi.Pattern != "*" { + pkgpath := p.Package.Path() + if strings.HasSuffix(pkgpath, "_test") { + pkgpath = pkgpath[:len(pkgpath)-len("_test")] + } + name := filepath.Join(pkgpath, filepath.Base(p.Position.Filename)) + if m, _ := filepath.Match(gi.Pattern, name); !m { + return false + } + } + for _, c := range gi.Checks { + if m, _ := filepath.Match(c, p.Check); m { + return true + } + } + return false +} + +type Program struct { + SSA *ssa.Program + Prog *loader.Program + // TODO(dh): Rename to InitialPackages? + Packages []*Pkg + InitialFunctions []*ssa.Function + AllFunctions []*ssa.Function + Files []*ast.File + Info *types.Info + GoVersion int + + tokenFileMap map[*token.File]*ast.File + astFileMap map[*ast.File]*Pkg +} + +type Func func(*Job) + +// 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 +} + +func (p *Problem) String() string { + if p.Check == "" { + return p.Text + } + return fmt.Sprintf("%s (%s)", p.Text, p.Check) +} + +type Checker interface { + Name() string + Prefix() string + Init(*Program) + Funcs() map[string]Func +} + +// A Linter lints Go source code. +type Linter struct { + Checker Checker + Ignores []Ignore + GoVersion int + ReturnIgnored bool + + automaticIgnores []Ignore +} + +func (l *Linter) ignore(p Problem) bool { + ignored := false + for _, ig := range l.automaticIgnores { + // We cannot short-circuit these, as we want to record, for + // each ignore, whether it matched or not. + if ig.Match(p) { + ignored = true + } + } + if ignored { + // no need to execute other ignores if we've already had a + // match. + return true + } + for _, ig := range l.Ignores { + // We can short-circuit here, as we aren't tracking any + // information. + if ig.Match(p) { + return true + } + } + + return false +} + +func (prog *Program) File(node Positioner) *ast.File { + return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())] +} + +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 + } + s = strings.TrimPrefix(s, "//lint:") + fields := strings.Split(s, " ") + return fields[0], fields[1:] +} + +func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem { + ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) + ssaprog.Build() + 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 + dir := filepath.Dir(path) + var err error + ctx := conf.Build + if ctx == nil { + ctx = &build.Default + } + bp, err = ctx.ImportDir(dir, 0) + if err != nil { + // shouldn't happen + } + } + pkg := &Pkg{ + Package: ssapkg, + Info: pkginfo, + BuildPkg: bp, + } + 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{}, + } + + initial := map[*types.Package]struct{}{} + for _, pkg := range pkgs { + initial[pkg.Info.Pkg] = 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 { + prog.InitialFunctions = append(prog.InitialFunctions, fn) + } + } + for _, pkg := range pkgs { + prog.Files = append(prog.Files, pkg.Info.Files...) + + ssapkg := ssaprog.Package(pkg.Info.Pkg) + for _, f := range pkg.Info.Files { + prog.astFileMap[f] = pkgMap[ssapkg] + } + } + + for _, pkginfo := range lprog.AllPackages { + for _, f := range pkginfo.Files { + tf := lprog.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 node, cgs := range cm { + for _, cg := range cgs { + for _, c := range cg.List { + if !strings.HasPrefix(c.Text, "//lint:") { + continue + } + cmd, args := parseDirective(c.Text) + switch cmd { + case "ignore", "file-ignore": + 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(), + Package: nil, + } + out = append(out, p) + continue + } + default: + // unknown directive, ignore + continue + } + checks := strings.Split(args[0], ",") + pos := prog.DisplayPosition(node.Pos()) + var ig Ignore + switch cmd { + case "ignore": + ig = &LineIgnore{ + File: pos.Filename, + Line: pos.Line, + Checks: checks, + pos: c.Pos(), + } + case "file-ignore": + ig = &FileIgnore{ + File: pos.Filename, + Checks: checks, + } + } + l.automaticIgnores = append(l.automaticIgnores, ig) + } + } + } + } + } + + sizes := struct { + types int + defs int + uses int + implicits int + selections int + 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) + } + 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) + } + sort.Strings(keys) + + var jobs []*Job + for _, k := range keys { + j := &Job{ + Program: prog, + checker: l.Checker.Name(), + check: k, + } + jobs = append(jobs, j) + } + wg := &sync.WaitGroup{} + for _, j := range jobs { + wg.Add(1) + go func(j *Job) { + defer wg.Done() + fn := funcs[j.check] + if fn == nil { + return + } + fn(j) + }(j) + } + wg.Wait() + + for _, j := range jobs { + for _, p := range j.problems { + p.Ignored = l.ignore(p) + if l.ReturnIgnored || !p.Ignored { + out = append(out, p) + } + } + } + + for _, ig := range l.automaticIgnores { + ig, ok := ig.(*LineIgnore) + if !ok { + continue + } + 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 + continue + } + if c[:idx] != l.Checker.Prefix() { + // not for this checker + continue + } + 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) + } + } + + sort.Sort(byPosition{lprog.Fset, out}) + return out +} + +// Pkg represents a package being linted. +type Pkg struct { + *ssa.Package + Info *loader.PackageInfo + BuildPkg *build.Package +} + +type packager interface { + Package() *ssa.Package +} + +func IsExample(fn *ssa.Function) bool { + if !strings.HasPrefix(fn.Name(), "Example") { + return false + } + f := fn.Prog.Fset.File(fn.Pos()) + if f == nil { + return false + } + return strings.HasSuffix(f.Name(), "_test.go") +} + +func (j *Job) IsInTest(node Positioner) bool { + f := j.Program.SSA.Fset.File(node.Pos()) + return f != nil && strings.HasSuffix(f.Name(), "_test.go") +} + +func (j *Job) IsInMain(node Positioner) bool { + if node, ok := node.(packager); ok { + return node.Package().Pkg.Name() == "main" + } + pkg := j.NodePackage(node) + if pkg == nil { + return false + } + return pkg.Pkg.Name() == "main" +} + +type Positioner interface { + Pos() token.Pos +} + +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 + + 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?) + 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 + } + } + // not a cgo file, ignore //line directives + return prog.Prog.Fset.PositionFor(p, false) +} + +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 + + pos := j.Program.DisplayPosition(n.Pos()) + problem := Problem{ + pos: n.Pos(), + Position: pos, + Text: fmt.Sprintf(format, args...), + Check: j.check, + Checker: j.checker, + Package: pkg, + } + j.problems = append(j.problems, problem) + return &j.problems[len(j.problems)-1] +} + +func (j *Job) Render(x interface{}) string { + fset := j.Program.SSA.Fset + var buf bytes.Buffer + if err := printer.Fprint(&buf, fset, x); err != nil { + panic(err) + } + return buf.String() +} + +func (j *Job) RenderArgs(args []ast.Expr) string { + var ss []string + for _, arg := range args { + ss = append(ss, j.Render(arg)) + } + return strings.Join(ss, ", ") +} + +func IsIdent(expr ast.Expr, ident string) bool { + id, ok := expr.(*ast.Ident) + return ok && id.Name == ident +} + +// isBlank returns whether id is the blank identifier "_". +// If id == nil, the answer is false. +func IsBlank(id ast.Expr) bool { + ident, ok := id.(*ast.Ident) + return ok && ident.Name == "_" +} + +func IsZero(expr ast.Expr) bool { + lit, ok := expr.(*ast.BasicLit) + return ok && lit.Kind == token.INT && lit.Value == "0" +} + +func (j *Job) IsNil(expr ast.Expr) bool { + return j.Program.Info.Types[expr].IsNil() +} + +func (j *Job) BoolConst(expr ast.Expr) bool { + val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() + return constant.BoolVal(val) +} + +func (j *Job) IsBoolConst(expr ast.Expr) bool { + // We explicitly don't support typed bools because more often than + // not, custom bool types are used as binary enums and the + // explicit comparison is desired. + + ident, ok := expr.(*ast.Ident) + if !ok { + return false + } + obj := j.Program.Info.ObjectOf(ident) + c, ok := obj.(*types.Const) + if !ok { + return false + } + basic, ok := c.Type().(*types.Basic) + if !ok { + return false + } + if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { + return false + } + return true +} + +func (j *Job) ExprToInt(expr ast.Expr) (int64, bool) { + tv := j.Program.Info.Types[expr] + if tv.Value == nil { + return 0, false + } + if tv.Value.Kind() != constant.Int { + return 0, false + } + return constant.Int64Val(tv.Value) +} + +func (j *Job) ExprToString(expr ast.Expr) (string, bool) { + val := j.Program.Info.Types[expr].Value + if val == nil { + return "", false + } + if val.Kind() != constant.String { + return "", false + } + return constant.StringVal(val), true +} + +func (j *Job) NodePackage(node Positioner) *Pkg { + f := j.File(node) + return j.Program.astFileMap[f] +} + +func IsGenerated(f *ast.File) bool { + comments := f.Comments + if len(comments) > 0 { + comment := comments[0].Text() + return strings.Contains(comment, "Code generated by") || + strings.Contains(comment, "DO NOT EDIT") + } + return false +} + +func Preamble(f *ast.File) string { + cutoff := f.Package + if f.Doc != nil { + cutoff = f.Doc.Pos() + } + var out []string + for _, cmt := range f.Comments { + if cmt.Pos() >= cutoff { + break + } + out = append(out, cmt.Text()) + } + return strings.Join(out, "\n") +} + +func IsPointerLike(T types.Type) bool { + switch T := T.Underlying().(type) { + case *types.Interface, *types.Chan, *types.Map, *types.Pointer: + return true + case *types.Basic: + return T.Kind() == types.UnsafePointer + } + return false +} + +func (j *Job) IsGoVersion(minor int) bool { + return j.Program.GoVersion >= minor +} + +func (j *Job) IsCallToAST(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 +} + +func (j *Job) IsCallToAnyAST(node ast.Node, names ...string) bool { + for _, name := range names { + if j.IsCallToAST(node, name) { + return true + } + } + return false +} + +func CallName(call *ssa.CallCommon) string { + if call.IsInvoke() { + return "" + } + switch v := call.Value.(type) { + case *ssa.Function: + fn, ok := v.Object().(*types.Func) + if !ok { + return "" + } + return fn.FullName() + case *ssa.Builtin: + return v.Name() + } + return "" +} + +func IsCallTo(call *ssa.CallCommon, name string) bool { + return CallName(call) == name +} + +func FilterDebug(instr []ssa.Instruction) []ssa.Instruction { + var out []ssa.Instruction + for _, ins := range instr { + if _, ok := ins.(*ssa.DebugRef); !ok { + out = append(out, ins) + } + } + return out +} + +func NodeFns(pkgs []*Pkg) map[ast.Node]*ssa.Function { + out := map[ast.Node]*ssa.Function{} + + wg := &sync.WaitGroup{} + chNodeFns := make(chan map[ast.Node]*ssa.Function, runtime.NumCPU()*2) + for _, pkg := range pkgs { + pkg := pkg + wg.Add(1) + go func() { + m := map[ast.Node]*ssa.Function{} + for _, f := range pkg.Info.Files { + ast.Walk(&globalVisitor{m, pkg, f}, f) + } + chNodeFns <- m + wg.Done() + }() + } + go func() { + wg.Wait() + close(chNodeFns) + }() + + for nodeFns := range chNodeFns { + for k, v := range nodeFns { + out[k] = v + } + } + + return out +} + +type globalVisitor struct { + m map[ast.Node]*ssa.Function + pkg *Pkg + f *ast.File +} + +func (v *globalVisitor) Visit(node ast.Node) ast.Visitor { + switch node := node.(type) { + case *ast.CallExpr: + v.m[node] = v.pkg.Func("init") + return v + case *ast.FuncDecl, *ast.FuncLit: + nv := &fnVisitor{v.m, v.f, v.pkg, nil} + return nv.Visit(node) + default: + return v + } +} + +type fnVisitor struct { + m map[ast.Node]*ssa.Function + f *ast.File + pkg *Pkg + ssafn *ssa.Function +} + +func (v *fnVisitor) Visit(node ast.Node) ast.Visitor { + switch node := node.(type) { + case *ast.FuncDecl: + var ssafn *ssa.Function + ssafn = v.pkg.Prog.FuncValue(v.pkg.Info.ObjectOf(node.Name).(*types.Func)) + v.m[node] = ssafn + if ssafn == nil { + return nil + } + return &fnVisitor{v.m, v.f, v.pkg, ssafn} + case *ast.FuncLit: + var ssafn *ssa.Function + path, _ := astutil.PathEnclosingInterval(v.f, node.Pos(), node.Pos()) + ssafn = ssa.EnclosingFunction(v.pkg.Package, path) + v.m[node] = ssafn + if ssafn == nil { + return nil + } + return &fnVisitor{v.m, v.f, v.pkg, ssafn} + case nil: + return nil + default: + v.m[node] = v.ssafn + return v + } +} |