| 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. |
| 7 | package analysisutil |
| 8 | |
| 9 | import ( |
| 10 | "bytes" |
| 11 | "go/ast" |
| 12 | "go/printer" |
| 13 | "go/token" |
| 14 | "go/types" |
| 15 | "io/ioutil" |
| 16 | ) |
| 17 | |
| 18 | // Format returns a string representation of the expression. |
| 19 | func Format(fset *token.FileSet, x ast.Expr) string { |
| 20 | var b bytes.Buffer |
| 21 | printer.Fprint(&b, fset, x) |
| 22 | return b.String() |
| 23 | } |
| 24 | |
| 25 | // HasSideEffects reports whether evaluation of e has side effects. |
| 26 | func HasSideEffects(info *types.Info, e ast.Expr) bool { |
| 27 | safe := true |
| 28 | ast.Inspect(e, func(node ast.Node) bool { |
| 29 | switch n := node.(type) { |
| 30 | case *ast.CallExpr: |
| 31 | typVal := info.Types[n.Fun] |
| 32 | switch { |
| 33 | case typVal.IsType(): |
| 34 | // Type conversion, which is safe. |
| 35 | case typVal.IsBuiltin(): |
| 36 | // Builtin func, conservatively assumed to not |
| 37 | // be safe for now. |
| 38 | safe = false |
| 39 | return false |
| 40 | default: |
| 41 | // A non-builtin func or method call. |
| 42 | // Conservatively assume that all of them have |
| 43 | // side effects for now. |
| 44 | safe = false |
| 45 | return false |
| 46 | } |
| 47 | case *ast.UnaryExpr: |
| 48 | if n.Op == token.ARROW { |
| 49 | safe = false |
| 50 | return false |
| 51 | } |
| 52 | } |
| 53 | return true |
| 54 | }) |
| 55 | return !safe |
| 56 | } |
| 57 | |
| 58 | // Unparen returns e with any enclosing parentheses stripped. |
| 59 | func Unparen(e ast.Expr) ast.Expr { |
| 60 | for { |
| 61 | p, ok := e.(*ast.ParenExpr) |
| 62 | if !ok { |
| 63 | return e |
| 64 | } |
| 65 | e = p.X |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | // ReadFile reads a file and adds it to the FileSet |
| 70 | // so that we can report errors against it using lineStart. |
| 71 | func ReadFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) { |
| 72 | content, err := ioutil.ReadFile(filename) |
| 73 | if err != nil { |
| 74 | return nil, nil, err |
| 75 | } |
| 76 | tf := fset.AddFile(filename, -1, len(content)) |
| 77 | tf.SetLinesForContent(content) |
| 78 | return content, tf, nil |
| 79 | } |
| 80 | |
| 81 | // LineStart returns the position of the start of the specified line |
| 82 | // within file f, or NoPos if there is no line of that number. |
| 83 | func LineStart(f *token.File, line int) token.Pos { |
| 84 | // Use binary search to find the start offset of this line. |
| 85 | // |
| 86 | // TODO(adonovan): eventually replace this function with the |
| 87 | // simpler and more efficient (*go/token.File).LineStart, added |
| 88 | // in go1.12. |
| 89 | |
| 90 | min := 0 // inclusive |
| 91 | max := f.Size() // exclusive |
| 92 | for { |
| 93 | offset := (min + max) / 2 |
| 94 | pos := f.Pos(offset) |
| 95 | posn := f.Position(pos) |
| 96 | if posn.Line == line { |
| 97 | return pos - (token.Pos(posn.Column) - 1) |
| 98 | } |
| 99 | |
| 100 | if min+1 >= max { |
| 101 | return token.NoPos |
| 102 | } |
| 103 | |
| 104 | if posn.Line < line { |
| 105 | min = offset |
| 106 | } else { |
| 107 | max = offset |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | // Imports returns true if path is imported by pkg. |
| 113 | func Imports(pkg *types.Package, path string) bool { |
| 114 | for _, imp := range pkg.Imports() { |
| 115 | if imp.Path() == path { |
| 116 | return true |
| 117 | } |
| 118 | } |
| 119 | return false |
| 120 | } |
| 121 |
Members