1 Star 0 Fork 0

lonely/gometalinter

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
lint.go 11.53 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
// 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/constant"
"go/printer"
"go/token"
"go/types"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"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
check string
problems []Problem
}
type Ignore struct {
Pattern string
Checks []string
}
type Program struct {
SSA *ssa.Program
Prog *loader.Program
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]*ssa.Package
}
type Func func(*Job)
// Problem represents a problem in some source code.
type Problem struct {
Position token.Pos // position in source file
Text string // the prose that describes the problem
}
func (p *Problem) String() string {
return p.Text
}
type Checker interface {
Init(*Program)
Funcs() map[string]Func
}
// A Linter lints Go source code.
type Linter struct {
Checker Checker
Ignores []Ignore
GoVersion int
}
func (l *Linter) ignore(j *Job, p Problem) bool {
tf := j.Program.SSA.Fset.File(p.Position)
f := j.Program.tokenFileMap[tf]
pkg := j.Program.astFileMap[f].Pkg
for _, ig := range l.Ignores {
pkgpath := pkg.Path()
if strings.HasSuffix(pkgpath, "_test") {
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
}
name := filepath.Join(pkgpath, filepath.Base(tf.Name()))
if m, _ := filepath.Match(ig.Pattern, name); !m {
continue
}
for _, c := range ig.Checks {
if m, _ := filepath.Match(c, j.check); m {
return true
}
}
}
return false
}
func (j *Job) File(node Positioner) *ast.File {
return j.Program.tokenFileMap[j.Program.SSA.Fset.File(node.Pos())]
}
// 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.fset.Position(ps.ps[i].Position), ps.fset.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 (l *Linter) Lint(lprog *loader.Program) []Problem {
ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ssaprog.Build()
var pkgs []*Pkg
for _, pkginfo := range lprog.InitialPackages() {
ssapkg := ssaprog.Package(pkginfo.Pkg)
pkg := &Pkg{
Package: ssapkg,
Info: pkginfo,
}
pkgs = append(pkgs, pkg)
}
prog := &Program{
SSA: ssaprog,
Prog: lprog,
Packages: pkgs,
Info: &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
Defs: map[*ast.Ident]types.Object{},
Uses: map[*ast.Ident]types.Object{},
Implicits: map[ast.Node]types.Object{},
Selections: map[*ast.SelectorExpr]*types.Selection{},
Scopes: map[ast.Node]*types.Scope{},
},
GoVersion: l.GoVersion,
tokenFileMap: map[*token.File]*ast.File{},
astFileMap: map[*ast.File]*ssa.Package{},
}
for fn := range ssautil.AllFunctions(ssaprog) {
prog.AllFunctions = append(prog.AllFunctions, fn)
// TODO(dh): optimize this function
for _, pkg := range lprog.InitialPackages() {
if fn.Pkg == nil {
continue
}
if fn.Pkg.Pkg == pkg.Pkg {
prog.InitialFunctions = append(prog.InitialFunctions, fn)
break
}
}
}
for _, pkginfo := range lprog.InitialPackages() {
prog.Files = append(prog.Files, pkginfo.Files...)
ssapkg := ssaprog.Package(pkginfo.Pkg)
for _, f := range pkginfo.Files {
tf := lprog.Fset.File(f.Pos())
prog.tokenFileMap[tf] = f
prog.astFileMap[f] = ssapkg
}
}
for _, pkginfo := range lprog.InitialPackages() {
for k, v := range pkginfo.Info.Types {
prog.Info.Types[k] = v
}
for k, v := range pkginfo.Info.Defs {
prog.Info.Defs[k] = v
}
for k, v := range pkginfo.Info.Uses {
prog.Info.Uses[k] = v
}
for k, v := range pkginfo.Info.Implicits {
prog.Info.Implicits[k] = v
}
for k, v := range pkginfo.Info.Selections {
prog.Info.Selections[k] = v
}
for k, v := range pkginfo.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,
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()
var out []Problem
for _, j := range jobs {
for _, p := range j.problems {
if !l.ignore(j, p) {
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
}
type packager interface {
Package() *ssa.Package
}
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 (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
problem := Problem{
Position: n.Pos(),
Text: fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", j.check),
}
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) *ssa.Package {
f := j.File(node)
return j.Program.astFileMap[f]
}
func (j *Job) EnclosingSSAFunction(node Positioner) *ssa.Function {
f := j.File(node)
path, _ := astutil.PathEnclosingInterval(f, node.Pos(), node.Pos())
pkg := j.Program.astFileMap[f]
return ssa.EnclosingFunction(pkg, path)
}
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 (j *Job) IsGoVersion(minor int) bool {
return j.Program.GoVersion >= minor
}
func (j *Job) IsFunctionCallName(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) IsFunctionCallNameAny(node ast.Node, names ...string) bool {
for _, name := range names {
if j.IsFunctionCallName(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
for _, f := range pkg.Info.Files {
f := f
wg.Add(1)
go func() {
m := map[ast.Node]*ssa.Function{}
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:
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
}
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/lonely0422/gometalinter.git
git@gitee.com:lonely0422/gometalinter.git
lonely0422
gometalinter
gometalinter
v1.2.1

搜索帮助