github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/lint/lintdsl/lintdsl.go (about)

     1  // Package lintdsl provides helpers for implementing static analysis
     2  // checks. Dot-importing this package is encouraged.
     3  package lintdsl
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"go/ast"
     9  	"go/constant"
    10  	"go/printer"
    11  	"go/token"
    12  	"go/types"
    13  	"strings"
    14  
    15  	"github.com/golangci/go-tools/lint"
    16  	"github.com/golangci/go-tools/ssa"
    17  )
    18  
    19  type packager interface {
    20  	Package() *ssa.Package
    21  }
    22  
    23  func CallName(call *ssa.CallCommon) string {
    24  	if call.IsInvoke() {
    25  		return ""
    26  	}
    27  	switch v := call.Value.(type) {
    28  	case *ssa.Function:
    29  		fn, ok := v.Object().(*types.Func)
    30  		if !ok {
    31  			return ""
    32  		}
    33  		return fn.FullName()
    34  	case *ssa.Builtin:
    35  		return v.Name()
    36  	}
    37  	return ""
    38  }
    39  
    40  func IsCallTo(call *ssa.CallCommon, name string) bool { return CallName(call) == name }
    41  func IsType(T types.Type, name string) bool           { return types.TypeString(T, nil) == name }
    42  
    43  func FilterDebug(instr []ssa.Instruction) []ssa.Instruction {
    44  	var out []ssa.Instruction
    45  	for _, ins := range instr {
    46  		if _, ok := ins.(*ssa.DebugRef); !ok {
    47  			out = append(out, ins)
    48  		}
    49  	}
    50  	return out
    51  }
    52  
    53  func IsExample(fn *ssa.Function) bool {
    54  	if !strings.HasPrefix(fn.Name(), "Example") {
    55  		return false
    56  	}
    57  	f := fn.Prog.Fset.File(fn.Pos())
    58  	if f == nil {
    59  		return false
    60  	}
    61  	return strings.HasSuffix(f.Name(), "_test.go")
    62  }
    63  
    64  func IsPointerLike(T types.Type) bool {
    65  	switch T := T.Underlying().(type) {
    66  	case *types.Interface, *types.Chan, *types.Map, *types.Pointer:
    67  		return true
    68  	case *types.Basic:
    69  		return T.Kind() == types.UnsafePointer
    70  	}
    71  	return false
    72  }
    73  
    74  func IsGenerated(f *ast.File) bool {
    75  	comments := f.Comments
    76  	if len(comments) > 0 {
    77  		comment := comments[0].Text()
    78  		return strings.Contains(comment, "Code generated by") ||
    79  			strings.Contains(comment, "DO NOT EDIT")
    80  	}
    81  	return false
    82  }
    83  
    84  func IsIdent(expr ast.Expr, ident string) bool {
    85  	id, ok := expr.(*ast.Ident)
    86  	return ok && id.Name == ident
    87  }
    88  
    89  // isBlank returns whether id is the blank identifier "_".
    90  // If id == nil, the answer is false.
    91  func IsBlank(id ast.Expr) bool {
    92  	ident, _ := id.(*ast.Ident)
    93  	return ident != nil && ident.Name == "_"
    94  }
    95  
    96  func IsIntLiteral(expr ast.Expr, literal string) bool {
    97  	lit, ok := expr.(*ast.BasicLit)
    98  	return ok && lit.Kind == token.INT && lit.Value == literal
    99  }
   100  
   101  // Deprecated: use IsIntLiteral instead
   102  func IsZero(expr ast.Expr) bool {
   103  	return IsIntLiteral(expr, "0")
   104  }
   105  
   106  func TypeOf(j *lint.Job, expr ast.Expr) types.Type {
   107  	if expr == nil {
   108  		return nil
   109  	}
   110  	return j.NodePackage(expr).TypesInfo.TypeOf(expr)
   111  }
   112  
   113  func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
   114  
   115  func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object {
   116  	if ident == nil {
   117  		return nil
   118  	}
   119  	return j.NodePackage(ident).TypesInfo.ObjectOf(ident)
   120  }
   121  
   122  func IsInTest(j *lint.Job, node lint.Positioner) bool {
   123  	// FIXME(dh): this doesn't work for global variables with
   124  	// initializers
   125  	f := j.Program.SSA.Fset.File(node.Pos())
   126  	return f != nil && strings.HasSuffix(f.Name(), "_test.go")
   127  }
   128  
   129  func IsInMain(j *lint.Job, node lint.Positioner) bool {
   130  	if node, ok := node.(packager); ok {
   131  		return node.Package().Pkg.Name() == "main"
   132  	}
   133  	pkg := j.NodePackage(node)
   134  	if pkg == nil {
   135  		return false
   136  	}
   137  	return pkg.Types.Name() == "main"
   138  }
   139  
   140  func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
   141  	info := j.NodePackage(expr).TypesInfo
   142  	sel := info.Selections[expr]
   143  	if sel == nil {
   144  		if x, ok := expr.X.(*ast.Ident); ok {
   145  			pkg, ok := info.ObjectOf(x).(*types.PkgName)
   146  			if !ok {
   147  				// This shouldn't happen
   148  				return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
   149  			}
   150  			return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
   151  		}
   152  		panic(fmt.Sprintf("unsupported selector: %v", expr))
   153  	}
   154  	return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
   155  }
   156  
   157  func IsNil(j *lint.Job, expr ast.Expr) bool {
   158  	return j.NodePackage(expr).TypesInfo.Types[expr].IsNil()
   159  }
   160  
   161  func BoolConst(j *lint.Job, expr ast.Expr) bool {
   162  	val := j.NodePackage(expr).TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
   163  	return constant.BoolVal(val)
   164  }
   165  
   166  func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
   167  	// We explicitly don't support typed bools because more often than
   168  	// not, custom bool types are used as binary enums and the
   169  	// explicit comparison is desired.
   170  
   171  	ident, ok := expr.(*ast.Ident)
   172  	if !ok {
   173  		return false
   174  	}
   175  	obj := j.NodePackage(expr).TypesInfo.ObjectOf(ident)
   176  	c, ok := obj.(*types.Const)
   177  	if !ok {
   178  		return false
   179  	}
   180  	basic, ok := c.Type().(*types.Basic)
   181  	if !ok {
   182  		return false
   183  	}
   184  	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
   185  		return false
   186  	}
   187  	return true
   188  }
   189  
   190  func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
   191  	tv := j.NodePackage(expr).TypesInfo.Types[expr]
   192  	if tv.Value == nil {
   193  		return 0, false
   194  	}
   195  	if tv.Value.Kind() != constant.Int {
   196  		return 0, false
   197  	}
   198  	return constant.Int64Val(tv.Value)
   199  }
   200  
   201  func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
   202  	val := j.NodePackage(expr).TypesInfo.Types[expr].Value
   203  	if val == nil {
   204  		return "", false
   205  	}
   206  	if val.Kind() != constant.String {
   207  		return "", false
   208  	}
   209  	return constant.StringVal(val), true
   210  }
   211  
   212  // Dereference returns a pointer's element type; otherwise it returns
   213  // T.
   214  func Dereference(T types.Type) types.Type {
   215  	if p, ok := T.Underlying().(*types.Pointer); ok {
   216  		return p.Elem()
   217  	}
   218  	return T
   219  }
   220  
   221  // DereferenceR returns a pointer's element type; otherwise it returns
   222  // T. If the element type is itself a pointer, DereferenceR will be
   223  // applied recursively.
   224  func DereferenceR(T types.Type) types.Type {
   225  	if p, ok := T.Underlying().(*types.Pointer); ok {
   226  		return DereferenceR(p.Elem())
   227  	}
   228  	return T
   229  }
   230  
   231  func IsGoVersion(j *lint.Job, minor int) bool {
   232  	return j.Program.GoVersion >= minor
   233  }
   234  
   235  func CallNameAST(j *lint.Job, call *ast.CallExpr) string {
   236  	sel, ok := call.Fun.(*ast.SelectorExpr)
   237  	if !ok {
   238  		return ""
   239  	}
   240  	fn, ok := j.NodePackage(call).TypesInfo.ObjectOf(sel.Sel).(*types.Func)
   241  	if !ok {
   242  		return ""
   243  	}
   244  	return fn.FullName()
   245  }
   246  
   247  func IsCallToAST(j *lint.Job, node ast.Node, name string) bool {
   248  	call, ok := node.(*ast.CallExpr)
   249  	if !ok {
   250  		return false
   251  	}
   252  	return CallNameAST(j, call) == name
   253  }
   254  
   255  func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
   256  	for _, name := range names {
   257  		if IsCallToAST(j, node, name) {
   258  			return true
   259  		}
   260  	}
   261  	return false
   262  }
   263  
   264  func Render(j *lint.Job, x interface{}) string {
   265  	fset := j.Program.SSA.Fset
   266  	var buf bytes.Buffer
   267  	if err := printer.Fprint(&buf, fset, x); err != nil {
   268  		panic(err)
   269  	}
   270  	return buf.String()
   271  }
   272  
   273  func RenderArgs(j *lint.Job, args []ast.Expr) string {
   274  	var ss []string
   275  	for _, arg := range args {
   276  		ss = append(ss, Render(j, arg))
   277  	}
   278  	return strings.Join(ss, ", ")
   279  }
   280  
   281  func Preamble(f *ast.File) string {
   282  	cutoff := f.Package
   283  	if f.Doc != nil {
   284  		cutoff = f.Doc.Pos()
   285  	}
   286  	var out []string
   287  	for _, cmt := range f.Comments {
   288  		if cmt.Pos() >= cutoff {
   289  			break
   290  		}
   291  		out = append(out, cmt.Text())
   292  	}
   293  	return strings.Join(out, "\n")
   294  }
   295  
   296  func Inspect(node ast.Node, fn func(node ast.Node) bool) {
   297  	if node == nil {
   298  		return
   299  	}
   300  	ast.Inspect(node, fn)
   301  }
   302  
   303  func GroupSpecs(j *lint.Job, specs []ast.Spec) [][]ast.Spec {
   304  	if len(specs) == 0 {
   305  		return nil
   306  	}
   307  	fset := j.Program.SSA.Fset
   308  	groups := make([][]ast.Spec, 1)
   309  	groups[0] = append(groups[0], specs[0])
   310  
   311  	for _, spec := range specs[1:] {
   312  		g := groups[len(groups)-1]
   313  		if fset.PositionFor(spec.Pos(), false).Line-1 !=
   314  			fset.PositionFor(g[len(g)-1].End(), false).Line {
   315  
   316  			groups = append(groups, nil)
   317  		}
   318  
   319  		groups[len(groups)-1] = append(groups[len(groups)-1], spec)
   320  	}
   321  
   322  	return groups
   323  }