visit.go (4057B)
1 package code 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/ast" 7 "go/format" 8 "go/types" 9 "iter" 10 "slices" 11 12 typeindexanalyzer "honnef.co/go/tools/internal/xtools-internal/analysis/typeindex" 13 "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" 14 "honnef.co/go/tools/pattern" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/analysis/passes/inspect" 18 "golang.org/x/tools/go/ast/inspector" 19 ) 20 21 var RequiredAnalyzers = []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer} 22 23 func Cursor(pass *analysis.Pass) inspector.Cursor { 24 return pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root() 25 } 26 27 func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) { 28 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn) 29 } 30 31 func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) { 32 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) { 33 if push { 34 fn(n, stack) 35 } 36 return true 37 }) 38 } 39 40 func Matches(pass *analysis.Pass, qs ...pattern.Pattern) iter.Seq2[ast.Node, *pattern.Matcher] { 41 return func(yield func(ast.Node, *pattern.Matcher) bool) { 42 for _, q := range qs { 43 if !CouldMatchAny(pass, q) { 44 continue 45 } 46 47 if len(q.RootCallSymbols) != 0 { 48 index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) 49 for _, isym := range q.RootCallSymbols { 50 var obj types.Object 51 if isym.Type == "" { 52 obj = index.Object(isym.Path, isym.Ident) 53 } else { 54 obj = index.Selection(isym.Path, isym.Type, isym.Ident) 55 } 56 for c := range index.Calls(obj) { 57 node := c.Node() 58 if m, ok := Match(pass, q, node); ok { 59 if !yield(node, m) { 60 return 61 } 62 } 63 } 64 } 65 } else { 66 ins := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 67 fn := func(node ast.Node, push bool) bool { 68 if !push { 69 return true 70 } 71 72 if m, ok := Match(pass, q, node); ok { 73 return yield(node, m) 74 } 75 return true 76 } 77 ins.Nodes(q.EntryNodes, fn) 78 } 79 } 80 } 81 } 82 83 func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) { 84 // Note that we ignore q.Relevant – callers of Match usually use 85 // AST inspectors that already filter on nodes we're interested 86 // in. 87 m := &pattern.Matcher{TypesInfo: pass.TypesInfo} 88 ok := m.Match(q, node) 89 return m, ok 90 } 91 92 func CouldMatchAny(pass *analysis.Pass, qs ...pattern.Pattern) bool { 93 index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) 94 var do func(node pattern.Node) bool 95 do = func(node pattern.Node) bool { 96 switch node := node.(type) { 97 case pattern.Any: 98 return true 99 case pattern.Or: 100 return slices.ContainsFunc(node.Nodes, do) 101 case pattern.And: 102 for _, child := range node.Nodes { 103 if !do(child) { 104 return false 105 } 106 } 107 return true 108 case pattern.IndexSymbol: 109 if node.Type == "" { 110 return index.Object(node.Path, node.Ident) != nil 111 } else { 112 return index.Selection(node.Path, node.Type, node.Ident) != nil 113 } 114 default: 115 panic(fmt.Sprintf("internal error: unexpected type %T", node)) 116 } 117 } 118 119 for _, q := range qs { 120 if do(q.SymbolsPattern) { 121 return true 122 } 123 } 124 return false 125 } 126 127 func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) { 128 m, ok := Match(pass, before, node) 129 if !ok { 130 return m, nil, false 131 } 132 r := pattern.NodeToAST(after.Root, m.State) 133 buf := &bytes.Buffer{} 134 format.Node(buf, pass.Fset, r) 135 edit := []analysis.TextEdit{{ 136 Pos: node.Pos(), 137 End: node.End(), 138 NewText: buf.Bytes(), 139 }} 140 return m, edit, true 141 } 142 143 func EditMatch(pass *analysis.Pass, node ast.Node, m *pattern.Matcher, after pattern.Pattern) []analysis.TextEdit { 144 r := pattern.NodeToAST(after.Root, m.State) 145 buf := &bytes.Buffer{} 146 format.Node(buf, pass.Fset, r) 147 edit := []analysis.TextEdit{{ 148 Pos: node.Pos(), 149 End: node.End(), 150 NewText: buf.Bytes(), 151 }} 152 return edit 153 }