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 }