| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- // Package code answers structural and type questions about Go code.
- package code
- import (
- "flag"
- "fmt"
- "go/ast"
- "go/constant"
- "go/token"
- "go/types"
- "strings"
- "honnef.co/go/tools/analysis/facts"
- "honnef.co/go/tools/go/ast/astutil"
- "honnef.co/go/tools/go/types/typeutil"
- "honnef.co/go/tools/pattern"
- "golang.org/x/exp/typeparams"
- "golang.org/x/tools/go/analysis"
- )
- type Positioner interface {
- Pos() token.Pos
- }
- func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
- return typeutil.IsType(pass.TypesInfo.TypeOf(expr), name)
- }
- func IsInTest(pass *analysis.Pass, node Positioner) bool {
- // FIXME(dh): this doesn't work for global variables with
- // initializers
- f := pass.Fset.File(node.Pos())
- return f != nil && strings.HasSuffix(f.Name(), "_test.go")
- }
- // IsMain reports whether the package being processed is a package
- // main.
- func IsMain(pass *analysis.Pass) bool {
- return pass.Pkg.Name() == "main"
- }
- // IsMainLike reports whether the package being processed is a
- // main-like package. A main-like package is a package that is
- // package main, or that is intended to be used by a tool framework
- // such as cobra to implement a command.
- //
- // Note that this function errs on the side of false positives; it may
- // return true for packages that aren't main-like. IsMainLike is
- // intended for analyses that wish to suppress diagnostics for
- // main-like packages to avoid false positives.
- func IsMainLike(pass *analysis.Pass) bool {
- if pass.Pkg.Name() == "main" {
- return true
- }
- for _, imp := range pass.Pkg.Imports() {
- if imp.Path() == "github.com/spf13/cobra" {
- return true
- }
- }
- return false
- }
- func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
- info := pass.TypesInfo
- sel := info.Selections[expr]
- if sel == nil {
- if x, ok := expr.X.(*ast.Ident); ok {
- pkg, ok := info.ObjectOf(x).(*types.PkgName)
- if !ok {
- // This shouldn't happen
- return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
- }
- return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
- }
- panic(fmt.Sprintf("unsupported selector: %v", expr))
- }
- if v, ok := sel.Obj().(*types.Var); ok && v.IsField() {
- return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name())
- } else {
- return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
- }
- }
- func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
- return pass.TypesInfo.Types[expr].IsNil()
- }
- func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
- val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
- return constant.BoolVal(val)
- }
- func IsBoolConst(pass *analysis.Pass, 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 := pass.TypesInfo.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 ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
- tv := pass.TypesInfo.Types[expr]
- if tv.Value == nil {
- return 0, false
- }
- if tv.Value.Kind() != constant.Int {
- return 0, false
- }
- return constant.Int64Val(tv.Value)
- }
- func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
- val := pass.TypesInfo.Types[expr].Value
- if val == nil {
- return "", false
- }
- if val.Kind() != constant.String {
- return "", false
- }
- return constant.StringVal(val), true
- }
- func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
- fun := astutil.Unparen(call.Fun)
- // Instantiating a function cannot return another generic function, so doing this once is enough
- switch idx := fun.(type) {
- case *ast.IndexExpr:
- fun = idx.X
- case *typeparams.IndexListExpr:
- fun = idx.X
- }
- // (foo)[T] is not a valid instantiationg, so no need to unparen again.
- switch fun := fun.(type) {
- case *ast.SelectorExpr:
- fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
- if !ok {
- return ""
- }
- return typeutil.FuncName(fn)
- case *ast.Ident:
- obj := pass.TypesInfo.ObjectOf(fun)
- switch obj := obj.(type) {
- case *types.Func:
- return typeutil.FuncName(obj)
- case *types.Builtin:
- return obj.Name()
- default:
- return ""
- }
- default:
- return ""
- }
- }
- func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
- call, ok := node.(*ast.CallExpr)
- if !ok {
- return false
- }
- return CallName(pass, call) == name
- }
- func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
- call, ok := node.(*ast.CallExpr)
- if !ok {
- return false
- }
- q := CallName(pass, call)
- for _, name := range names {
- if q == name {
- return true
- }
- }
- return false
- }
- func File(pass *analysis.Pass, node Positioner) *ast.File {
- m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File)
- return m[pass.Fset.File(node.Pos())]
- }
- // IsGenerated reports whether pos is in a generated file, It ignores
- // //line directives.
- func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
- _, ok := Generator(pass, pos)
- return ok
- }
- // Generator returns the generator that generated the file containing
- // pos. It ignores //line directives.
- func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) {
- file := pass.Fset.PositionFor(pos, false).Filename
- m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
- g, ok := m[file]
- return g, ok
- }
- // MayHaveSideEffects reports whether expr may have side effects. If
- // the purity argument is nil, this function implements a purely
- // syntactic check, meaning that any function call may have side
- // effects, regardless of the called function's body. Otherwise,
- // purity will be consulted to determine the purity of function calls.
- func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool {
- switch expr := expr.(type) {
- case *ast.BadExpr:
- return true
- case *ast.Ellipsis:
- return MayHaveSideEffects(pass, expr.Elt, purity)
- case *ast.FuncLit:
- // the literal itself cannot have side effects, only calling it
- // might, which is handled by CallExpr.
- return false
- case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
- // types cannot have side effects
- return false
- case *ast.BasicLit:
- return false
- case *ast.BinaryExpr:
- return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
- case *ast.CallExpr:
- if purity == nil {
- return true
- }
- switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
- case *types.Func:
- if _, ok := purity[obj]; !ok {
- return true
- }
- case *types.Builtin:
- switch obj.Name() {
- case "len", "cap":
- default:
- return true
- }
- default:
- return true
- }
- for _, arg := range expr.Args {
- if MayHaveSideEffects(pass, arg, purity) {
- return true
- }
- }
- return false
- case *ast.CompositeLit:
- if MayHaveSideEffects(pass, expr.Type, purity) {
- return true
- }
- for _, elt := range expr.Elts {
- if MayHaveSideEffects(pass, elt, purity) {
- return true
- }
- }
- return false
- case *ast.Ident:
- return false
- case *ast.IndexExpr:
- return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
- case *typeparams.IndexListExpr:
- // In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in
- // being safe.
- if MayHaveSideEffects(pass, expr.X, purity) {
- return true
- }
- for _, idx := range expr.Indices {
- if MayHaveSideEffects(pass, idx, purity) {
- return true
- }
- }
- return false
- case *ast.KeyValueExpr:
- return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
- case *ast.SelectorExpr:
- return MayHaveSideEffects(pass, expr.X, purity)
- case *ast.SliceExpr:
- return MayHaveSideEffects(pass, expr.X, purity) ||
- MayHaveSideEffects(pass, expr.Low, purity) ||
- MayHaveSideEffects(pass, expr.High, purity) ||
- MayHaveSideEffects(pass, expr.Max, purity)
- case *ast.StarExpr:
- return MayHaveSideEffects(pass, expr.X, purity)
- case *ast.TypeAssertExpr:
- return MayHaveSideEffects(pass, expr.X, purity)
- case *ast.UnaryExpr:
- if MayHaveSideEffects(pass, expr.X, purity) {
- return true
- }
- return expr.Op == token.ARROW || expr.Op == token.AND
- case *ast.ParenExpr:
- return MayHaveSideEffects(pass, expr.X, purity)
- case nil:
- return false
- default:
- panic(fmt.Sprintf("internal error: unhandled type %T", expr))
- }
- }
- func IsGoVersion(pass *analysis.Pass, minor int) bool {
- f, ok := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter)
- if !ok {
- panic("requested Go version, but analyzer has no version flag")
- }
- version := f.Get().(int)
- return version >= minor
- }
- var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`)
- func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) {
- m, ok := Match(pass, integerLiteralQ, node)
- if !ok {
- return types.TypeAndValue{}, false
- }
- return m.State["tv"].(types.TypeAndValue), true
- }
- func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool {
- tv, ok := IntegerLiteral(pass, node)
- if !ok {
- return false
- }
- return constant.Compare(tv.Value, token.EQL, value)
- }
|