Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/fmtappendf.go

     1  // Copyright 2024 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 modernize
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/types"
    11  	"strings"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  	"golang.org/x/tools/go/ast/edge"
    16  	"golang.org/x/tools/internal/analysis/analyzerutil"
    17  	typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
    18  	"golang.org/x/tools/internal/astutil"
    19  	"golang.org/x/tools/internal/typesinternal/typeindex"
    20  	"golang.org/x/tools/internal/versions"
    21  )
    22  
    23  var FmtAppendfAnalyzer = &analysis.Analyzer{
    24  	Name: "fmtappendf",
    25  	Doc:  analyzerutil.MustExtractDoc(doc, "fmtappendf"),
    26  	Requires: []*analysis.Analyzer{
    27  		inspect.Analyzer,
    28  		typeindexanalyzer.Analyzer,
    29  	},
    30  	Run: fmtappendf,
    31  	URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#fmtappendf",
    32  }
    33  
    34  // The fmtappend function replaces []byte(fmt.Sprintf(...)) by
    35  // fmt.Appendf(nil, ...), and similarly for Sprint, Sprintln.
    36  func fmtappendf(pass *analysis.Pass) (any, error) {
    37  	index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
    38  	for _, fn := range []types.Object{
    39  		index.Object("fmt", "Sprintf"),
    40  		index.Object("fmt", "Sprintln"),
    41  		index.Object("fmt", "Sprint"),
    42  	} {
    43  		for curCall := range index.Calls(fn) {
    44  			call := curCall.Node().(*ast.CallExpr)
    45  			if ek, idx := curCall.ParentEdge(); ek == edge.CallExpr_Args && idx == 0 {
    46  				// Is parent a T(fmt.SprintX(...)) conversion?
    47  				conv := curCall.Parent().Node().(*ast.CallExpr)
    48  				tv := pass.TypesInfo.Types[conv.Fun]
    49  				if tv.IsType() && types.Identical(tv.Type, byteSliceType) &&
    50  					analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_19) {
    51  					// Have: []byte(fmt.SprintX(...))
    52  
    53  					// Find "Sprint" identifier.
    54  					var id *ast.Ident
    55  					switch e := ast.Unparen(call.Fun).(type) {
    56  					case *ast.SelectorExpr:
    57  						id = e.Sel // "fmt.Sprint"
    58  					case *ast.Ident:
    59  						id = e // "Sprint" after `import . "fmt"`
    60  					}
    61  
    62  					old, new := fn.Name(), strings.Replace(fn.Name(), "Sprint", "Append", 1)
    63  					edits := []analysis.TextEdit{
    64  						{
    65  							// delete "[]byte("
    66  							Pos: conv.Pos(),
    67  							End: conv.Lparen + 1,
    68  						},
    69  						{
    70  							// remove ")"
    71  							Pos: conv.Rparen,
    72  							End: conv.Rparen + 1,
    73  						},
    74  						{
    75  							Pos:     id.Pos(),
    76  							End:     id.End(),
    77  							NewText: []byte(new),
    78  						},
    79  						{
    80  							Pos:     call.Lparen + 1,
    81  							NewText: []byte("nil, "),
    82  						},
    83  					}
    84  					if len(conv.Args) == 1 {
    85  						arg := conv.Args[0]
    86  						// Determine if we have T(fmt.SprintX(...)<non-args,
    87  						// like a space or a comma>). If so, delete the non-args
    88  						// that come before the right parenthesis. Leaving an
    89  						// extra comma here produces invalid code. (See
    90  						// golang/go#74709)
    91  						if arg.End() < conv.Rparen {
    92  							edits = append(edits, analysis.TextEdit{
    93  								Pos: arg.End(),
    94  								End: conv.Rparen,
    95  							})
    96  						}
    97  					}
    98  					pass.Report(analysis.Diagnostic{
    99  						Pos:     conv.Pos(),
   100  						End:     conv.End(),
   101  						Message: fmt.Sprintf("Replace []byte(fmt.%s...) with fmt.%s", old, new),
   102  						SuggestedFixes: []analysis.SuggestedFix{{
   103  							Message:   fmt.Sprintf("Replace []byte(fmt.%s...) with fmt.%s", old, new),
   104  							TextEdits: edits,
   105  						}},
   106  					})
   107  				}
   108  			}
   109  		}
   110  	}
   111  	return nil, nil
   112  }
   113  

View as plain text