1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package analysisutil defines various helper functions
6// used by two or more packages beneath go/analysis.
7package analysisutil
8
9import (
10	"bytes"
11	"go/ast"
12	"go/printer"
13	"go/token"
14	"go/types"
15	"os"
16
17	"golang.org/x/tools/go/analysis"
18	"golang.org/x/tools/internal/aliases"
19	"golang.org/x/tools/internal/analysisinternal"
20)
21
22// Format returns a string representation of the expression.
23func Format(fset *token.FileSet, x ast.Expr) string {
24	var b bytes.Buffer
25	printer.Fprint(&b, fset, x)
26	return b.String()
27}
28
29// HasSideEffects reports whether evaluation of e has side effects.
30func HasSideEffects(info *types.Info, e ast.Expr) bool {
31	safe := true
32	ast.Inspect(e, func(node ast.Node) bool {
33		switch n := node.(type) {
34		case *ast.CallExpr:
35			typVal := info.Types[n.Fun]
36			switch {
37			case typVal.IsType():
38				// Type conversion, which is safe.
39			case typVal.IsBuiltin():
40				// Builtin func, conservatively assumed to not
41				// be safe for now.
42				safe = false
43				return false
44			default:
45				// A non-builtin func or method call.
46				// Conservatively assume that all of them have
47				// side effects for now.
48				safe = false
49				return false
50			}
51		case *ast.UnaryExpr:
52			if n.Op == token.ARROW {
53				safe = false
54				return false
55			}
56		}
57		return true
58	})
59	return !safe
60}
61
62// ReadFile reads a file and adds it to the FileSet
63// so that we can report errors against it using lineStart.
64func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {
65	readFile := pass.ReadFile
66	if readFile == nil {
67		readFile = os.ReadFile
68	}
69	content, err := readFile(filename)
70	if err != nil {
71		return nil, nil, err
72	}
73	tf := pass.Fset.AddFile(filename, -1, len(content))
74	tf.SetLinesForContent(content)
75	return content, tf, nil
76}
77
78// LineStart returns the position of the start of the specified line
79// within file f, or NoPos if there is no line of that number.
80func LineStart(f *token.File, line int) token.Pos {
81	// Use binary search to find the start offset of this line.
82	//
83	// TODO(adonovan): eventually replace this function with the
84	// simpler and more efficient (*go/token.File).LineStart, added
85	// in go1.12.
86
87	min := 0        // inclusive
88	max := f.Size() // exclusive
89	for {
90		offset := (min + max) / 2
91		pos := f.Pos(offset)
92		posn := f.Position(pos)
93		if posn.Line == line {
94			return pos - (token.Pos(posn.Column) - 1)
95		}
96
97		if min+1 >= max {
98			return token.NoPos
99		}
100
101		if posn.Line < line {
102			min = offset
103		} else {
104			max = offset
105		}
106	}
107}
108
109// Imports returns true if path is imported by pkg.
110func Imports(pkg *types.Package, path string) bool {
111	for _, imp := range pkg.Imports() {
112		if imp.Path() == path {
113			return true
114		}
115	}
116	return false
117}
118
119// IsNamedType reports whether t is the named type with the given package path
120// and one of the given names.
121// This function avoids allocating the concatenation of "pkg.Name",
122// which is important for the performance of syntax matching.
123func IsNamedType(t types.Type, pkgPath string, names ...string) bool {
124	n, ok := aliases.Unalias(t).(*types.Named)
125	if !ok {
126		return false
127	}
128	obj := n.Obj()
129	if obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != pkgPath {
130		return false
131	}
132	name := obj.Name()
133	for _, n := range names {
134		if name == n {
135			return true
136		}
137	}
138	return false
139}
140
141// IsFunctionNamed reports whether f is a top-level function defined in the
142// given package and has one of the given names.
143// It returns false if f is nil or a method.
144func IsFunctionNamed(f *types.Func, pkgPath string, names ...string) bool {
145	if f == nil {
146		return false
147	}
148	if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
149		return false
150	}
151	if f.Type().(*types.Signature).Recv() != nil {
152		return false
153	}
154	for _, n := range names {
155		if f.Name() == n {
156			return true
157		}
158	}
159	return false
160}
161
162var MustExtractDoc = analysisinternal.MustExtractDoc
163