golang.org/x/tools@v0.21.0/internal/refactor/inline/analyzer/analyzer.go (about) 1 // Copyright 2023 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 //go:build go1.20 6 7 package analyzer 8 9 import ( 10 "fmt" 11 "go/ast" 12 "go/token" 13 "go/types" 14 "os" 15 "strings" 16 17 "golang.org/x/tools/go/analysis" 18 "golang.org/x/tools/go/analysis/passes/inspect" 19 "golang.org/x/tools/go/ast/inspector" 20 "golang.org/x/tools/go/types/typeutil" 21 "golang.org/x/tools/internal/diff" 22 "golang.org/x/tools/internal/refactor/inline" 23 ) 24 25 const Doc = `inline calls to functions with "inlineme" doc comment` 26 27 var Analyzer = &analysis.Analyzer{ 28 Name: "inline", 29 Doc: Doc, 30 URL: "https://pkg.go.dev/golang.org/x/tools/internal/refactor/inline/analyzer", 31 Run: run, 32 FactTypes: []analysis.Fact{new(inlineMeFact)}, 33 Requires: []*analysis.Analyzer{inspect.Analyzer}, 34 } 35 36 func run(pass *analysis.Pass) (interface{}, error) { 37 // Memoize repeated calls for same file. 38 // TODO(adonovan): the analysis.Pass should abstract this (#62292) 39 // as the driver may not be reading directly from the file system. 40 fileContent := make(map[string][]byte) 41 readFile := func(node ast.Node) ([]byte, error) { 42 filename := pass.Fset.File(node.Pos()).Name() 43 content, ok := fileContent[filename] 44 if !ok { 45 var err error 46 content, err = os.ReadFile(filename) 47 if err != nil { 48 return nil, err 49 } 50 fileContent[filename] = content 51 } 52 return content, nil 53 } 54 55 // Pass 1: find functions annotated with an "inlineme" 56 // comment, and export a fact for each one. 57 inlinable := make(map[*types.Func]*inline.Callee) // memoization of fact import (nil => no fact) 58 for _, file := range pass.Files { 59 for _, decl := range file.Decls { 60 if decl, ok := decl.(*ast.FuncDecl); ok { 61 // TODO(adonovan): this is just a placeholder. 62 // Use the precise go:fix syntax in the proposal. 63 // Beware that //go: comments are treated specially 64 // by (*ast.CommentGroup).Text(). 65 // TODO(adonovan): alternatively, consider using 66 // the universal annotation mechanism sketched in 67 // https://go.dev/cl/489835 (which doesn't yet have 68 // a proper proposal). 69 if strings.Contains(decl.Doc.Text(), "inlineme") { 70 content, err := readFile(file) 71 if err != nil { 72 pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err) 73 continue 74 } 75 callee, err := inline.AnalyzeCallee(discard, pass.Fset, pass.Pkg, pass.TypesInfo, decl, content) 76 if err != nil { 77 pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err) 78 continue 79 } 80 fn := pass.TypesInfo.Defs[decl.Name].(*types.Func) 81 pass.ExportObjectFact(fn, &inlineMeFact{callee}) 82 inlinable[fn] = callee 83 } 84 } 85 } 86 } 87 88 // Pass 2. Inline each static call to an inlinable function. 89 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 90 nodeFilter := []ast.Node{ 91 (*ast.File)(nil), 92 (*ast.CallExpr)(nil), 93 } 94 var currentFile *ast.File 95 inspect.Preorder(nodeFilter, func(n ast.Node) { 96 if file, ok := n.(*ast.File); ok { 97 currentFile = file 98 return 99 } 100 call := n.(*ast.CallExpr) 101 if fn := typeutil.StaticCallee(pass.TypesInfo, call); fn != nil { 102 // Inlinable? 103 callee, ok := inlinable[fn] 104 if !ok { 105 var fact inlineMeFact 106 if pass.ImportObjectFact(fn, &fact) { 107 callee = fact.Callee 108 inlinable[fn] = callee 109 } 110 } 111 if callee == nil { 112 return // nope 113 } 114 115 // Inline the call. 116 content, err := readFile(call) 117 if err != nil { 118 pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err) 119 return 120 } 121 caller := &inline.Caller{ 122 Fset: pass.Fset, 123 Types: pass.Pkg, 124 Info: pass.TypesInfo, 125 File: currentFile, 126 Call: call, 127 Content: content, 128 } 129 res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard}) 130 if err != nil { 131 pass.Reportf(call.Lparen, "%v", err) 132 return 133 } 134 got := res.Content 135 136 // Suggest the "fix". 137 var textEdits []analysis.TextEdit 138 for _, edit := range diff.Bytes(content, got) { 139 textEdits = append(textEdits, analysis.TextEdit{ 140 Pos: currentFile.FileStart + token.Pos(edit.Start), 141 End: currentFile.FileStart + token.Pos(edit.End), 142 NewText: []byte(edit.New), 143 }) 144 } 145 msg := fmt.Sprintf("inline call of %v", callee) 146 pass.Report(analysis.Diagnostic{ 147 Pos: call.Pos(), 148 End: call.End(), 149 Message: msg, 150 SuggestedFixes: []analysis.SuggestedFix{{ 151 Message: msg, 152 TextEdits: textEdits, 153 }}, 154 }) 155 } 156 }) 157 158 return nil, nil 159 } 160 161 type inlineMeFact struct{ Callee *inline.Callee } 162 163 func (f *inlineMeFact) String() string { return "inlineme " + f.Callee.String() } 164 func (*inlineMeFact) AFact() {} 165 166 func discard(string, ...any) {}