src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }