1// Package code answers structural and type questions about Go code.
2package code
3
4import (
5	"flag"
6	"fmt"
7	"go/ast"
8	"go/constant"
9	"go/token"
10	"go/types"
11	"strings"
12
13	"honnef.co/go/tools/analysis/facts"
14	"honnef.co/go/tools/go/types/typeutil"
15
16	"golang.org/x/tools/go/analysis"
17	"golang.org/x/tools/go/ast/astutil"
18)
19
20type Positioner interface {
21	Pos() token.Pos
22}
23
24func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
25	return typeutil.IsType(pass.TypesInfo.TypeOf(expr), name)
26}
27
28func IsInTest(pass *analysis.Pass, node Positioner) bool {
29	// FIXME(dh): this doesn't work for global variables with
30	// initializers
31	f := pass.Fset.File(node.Pos())
32	return f != nil && strings.HasSuffix(f.Name(), "_test.go")
33}
34
35// IsMain reports whether the package being processed is a package
36// main.
37func IsMain(pass *analysis.Pass) bool {
38	return pass.Pkg.Name() == "main"
39}
40
41// IsMainLike reports whether the package being processed is a
42// main-like package. A main-like package is a package that is
43// package main, or that is intended to be used by a tool framework
44// such as cobra to implement a command.
45//
46// Note that this function errs on the side of false positives; it may
47// return true for packages that aren't main-like. IsMainLike is
48// intended for analyses that wish to suppress diagnostics for
49// main-like packages to avoid false positives.
50func IsMainLike(pass *analysis.Pass) bool {
51	if pass.Pkg.Name() == "main" {
52		return true
53	}
54	for _, imp := range pass.Pkg.Imports() {
55		if imp.Path() == "github.com/spf13/cobra" {
56			return true
57		}
58	}
59	return false
60}
61
62func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
63	info := pass.TypesInfo
64	sel := info.Selections[expr]
65	if sel == nil {
66		if x, ok := expr.X.(*ast.Ident); ok {
67			pkg, ok := info.ObjectOf(x).(*types.PkgName)
68			if !ok {
69				// This shouldn't happen
70				return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
71			}
72			return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
73		}
74		panic(fmt.Sprintf("unsupported selector: %v", expr))
75	}
76	return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
77}
78
79func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
80	return pass.TypesInfo.Types[expr].IsNil()
81}
82
83func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
84	val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
85	return constant.BoolVal(val)
86}
87
88func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
89	// We explicitly don't support typed bools because more often than
90	// not, custom bool types are used as binary enums and the
91	// explicit comparison is desired.
92
93	ident, ok := expr.(*ast.Ident)
94	if !ok {
95		return false
96	}
97	obj := pass.TypesInfo.ObjectOf(ident)
98	c, ok := obj.(*types.Const)
99	if !ok {
100		return false
101	}
102	basic, ok := c.Type().(*types.Basic)
103	if !ok {
104		return false
105	}
106	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
107		return false
108	}
109	return true
110}
111
112func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
113	tv := pass.TypesInfo.Types[expr]
114	if tv.Value == nil {
115		return 0, false
116	}
117	if tv.Value.Kind() != constant.Int {
118		return 0, false
119	}
120	return constant.Int64Val(tv.Value)
121}
122
123func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
124	val := pass.TypesInfo.Types[expr].Value
125	if val == nil {
126		return "", false
127	}
128	if val.Kind() != constant.String {
129		return "", false
130	}
131	return constant.StringVal(val), true
132}
133
134func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
135	switch fun := astutil.Unparen(call.Fun).(type) {
136	case *ast.SelectorExpr:
137		fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
138		if !ok {
139			return ""
140		}
141		return typeutil.FuncName(fn)
142	case *ast.Ident:
143		obj := pass.TypesInfo.ObjectOf(fun)
144		switch obj := obj.(type) {
145		case *types.Func:
146			return typeutil.FuncName(obj)
147		case *types.Builtin:
148			return obj.Name()
149		default:
150			return ""
151		}
152	default:
153		return ""
154	}
155}
156
157func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
158	call, ok := node.(*ast.CallExpr)
159	if !ok {
160		return false
161	}
162	return CallName(pass, call) == name
163}
164
165func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
166	call, ok := node.(*ast.CallExpr)
167	if !ok {
168		return false
169	}
170	q := CallName(pass, call)
171	for _, name := range names {
172		if q == name {
173			return true
174		}
175	}
176	return false
177}
178
179func File(pass *analysis.Pass, node Positioner) *ast.File {
180	m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File)
181	return m[pass.Fset.File(node.Pos())]
182}
183
184// IsGenerated reports whether pos is in a generated file, It ignores
185// //line directives.
186func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
187	_, ok := Generator(pass, pos)
188	return ok
189}
190
191// Generator returns the generator that generated the file containing
192// pos. It ignores //line directives.
193func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) {
194	file := pass.Fset.PositionFor(pos, false).Filename
195	m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
196	g, ok := m[file]
197	return g, ok
198}
199
200// MayHaveSideEffects reports whether expr may have side effects. If
201// the purity argument is nil, this function implements a purely
202// syntactic check, meaning that any function call may have side
203// effects, regardless of the called function's body. Otherwise,
204// purity will be consulted to determine the purity of function calls.
205func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool {
206	switch expr := expr.(type) {
207	case *ast.BadExpr:
208		return true
209	case *ast.Ellipsis:
210		return MayHaveSideEffects(pass, expr.Elt, purity)
211	case *ast.FuncLit:
212		// the literal itself cannot have side ffects, only calling it
213		// might, which is handled by CallExpr.
214		return false
215	case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
216		// types cannot have side effects
217		return false
218	case *ast.BasicLit:
219		return false
220	case *ast.BinaryExpr:
221		return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
222	case *ast.CallExpr:
223		if purity == nil {
224			return true
225		}
226		switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
227		case *types.Func:
228			if _, ok := purity[obj]; !ok {
229				return true
230			}
231		case *types.Builtin:
232			switch obj.Name() {
233			case "len", "cap":
234			default:
235				return true
236			}
237		default:
238			return true
239		}
240		for _, arg := range expr.Args {
241			if MayHaveSideEffects(pass, arg, purity) {
242				return true
243			}
244		}
245		return false
246	case *ast.CompositeLit:
247		if MayHaveSideEffects(pass, expr.Type, purity) {
248			return true
249		}
250		for _, elt := range expr.Elts {
251			if MayHaveSideEffects(pass, elt, purity) {
252				return true
253			}
254		}
255		return false
256	case *ast.Ident:
257		return false
258	case *ast.IndexExpr:
259		return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
260	case *ast.KeyValueExpr:
261		return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
262	case *ast.SelectorExpr:
263		return MayHaveSideEffects(pass, expr.X, purity)
264	case *ast.SliceExpr:
265		return MayHaveSideEffects(pass, expr.X, purity) ||
266			MayHaveSideEffects(pass, expr.Low, purity) ||
267			MayHaveSideEffects(pass, expr.High, purity) ||
268			MayHaveSideEffects(pass, expr.Max, purity)
269	case *ast.StarExpr:
270		return MayHaveSideEffects(pass, expr.X, purity)
271	case *ast.TypeAssertExpr:
272		return MayHaveSideEffects(pass, expr.X, purity)
273	case *ast.UnaryExpr:
274		if MayHaveSideEffects(pass, expr.X, purity) {
275			return true
276		}
277		return expr.Op == token.ARROW
278	case *ast.ParenExpr:
279		return MayHaveSideEffects(pass, expr.X, purity)
280	case nil:
281		return false
282	default:
283		panic(fmt.Sprintf("internal error: unhandled type %T", expr))
284	}
285}
286
287func IsGoVersion(pass *analysis.Pass, minor int) bool {
288	f, ok := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter)
289	if !ok {
290		panic("requested Go version, but analyzer has no version flag")
291	}
292	version := f.Get().(int)
293	return version >= minor
294}
295