blob: 3f01b3b55dcf357db8bc1f024f514cf1efd5c2dc [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package analysisutil defines various helper functions
// used by two or more packages beneath go/analysis.
package analysisutil
import (
"bytes"
"go/ast"
"go/printer"
"go/token"
"go/types"
"os"
"golang.org/x/tools/internal/analysisinternal"
)
// Format returns a string representation of the expression.
func Format(fset *token.FileSet, x ast.Expr) string {
var b bytes.Buffer
printer.Fprint(&b, fset, x)
return b.String()
}
// HasSideEffects reports whether evaluation of e has side effects.
func HasSideEffects(info *types.Info, e ast.Expr) bool {
safe := true
ast.Inspect(e, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
typVal := info.Types[n.Fun]
switch {
case typVal.IsType():
// Type conversion, which is safe.
case typVal.IsBuiltin():
// Builtin func, conservatively assumed to not
// be safe for now.
safe = false
return false
default:
// A non-builtin func or method call.
// Conservatively assume that all of them have
// side effects for now.
safe = false
return false
}
case *ast.UnaryExpr:
if n.Op == token.ARROW {
safe = false
return false
}
}
return true
})
return !safe
}
// ReadFile reads a file and adds it to the FileSet
// so that we can report errors against it using lineStart.
func ReadFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, nil, err
}
tf := fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)
return content, tf, nil
}
// LineStart returns the position of the start of the specified line
// within file f, or NoPos if there is no line of that number.
func LineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
//
// TODO(adonovan): eventually replace this function with the
// simpler and more efficient (*go/token.File).LineStart, added
// in go1.12.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}
// Imports returns true if path is imported by pkg.
func Imports(pkg *types.Package, path string) bool {
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return true
}
}
return false
}
// IsNamedType reports whether t is the named type with the given package path
// and one of the given names.
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsNamedType(t types.Type, pkgPath string, names ...string) bool {
n, ok := t.(*types.Named)
if !ok {
return false
}
obj := n.Obj()
if obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != pkgPath {
return false
}
name := obj.Name()
for _, n := range names {
if name == n {
return true
}
}
return false
}
// IsFunctionNamed reports whether f is a top-level function defined in the
// given package and has one of the given names.
// It returns false if f is nil or a method.
func IsFunctionNamed(f *types.Func, pkgPath string, names ...string) bool {
if f == nil {
return false
}
if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
return false
}
if f.Type().(*types.Signature).Recv() != nil {
return false
}
for _, n := range names {
if f.Name() == n {
return true
}
}
return false
}
var MustExtractDoc = analysisinternal.MustExtractDoc