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) {}