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