github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1030/s1030.go (about)

     1  package s1030
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/edit"
    10  	"github.com/amarpal/go-tools/analysis/facts/generated"
    11  	"github.com/amarpal/go-tools/analysis/lint"
    12  	"github.com/amarpal/go-tools/analysis/report"
    13  	"github.com/amarpal/go-tools/pattern"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  )
    18  
    19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    20  	Analyzer: &analysis.Analyzer{
    21  		Name:     "S1030",
    22  		Run:      run,
    23  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
    24  	},
    25  	Doc: &lint.Documentation{
    26  		Title: `Use \'bytes.Buffer.String\' or \'bytes.Buffer.Bytes\'`,
    27  		Text: `\'bytes.Buffer\' has both a \'String\' and a \'Bytes\' method. It is almost never
    28  necessary to use \'string(buf.Bytes())\' or \'[]byte(buf.String())\' – simply
    29  use the other method.
    30  
    31  The only exception to this are map lookups. Due to a compiler optimization,
    32  \'m[string(buf.Bytes())]\' is more efficient than \'m[buf.String()]\'.
    33  `,
    34  		Since:   "2017.1",
    35  		MergeIf: lint.MergeIfAny,
    36  	},
    37  })
    38  
    39  var Analyzer = SCAnalyzer.Analyzer
    40  
    41  var (
    42  	checkBytesBufferConversionsQ  = pattern.MustParse(`(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])`)
    43  	checkBytesBufferConversionsRs = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "String")) [])`)
    44  	checkBytesBufferConversionsRb = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "Bytes")) [])`)
    45  )
    46  
    47  func run(pass *analysis.Pass) (interface{}, error) {
    48  	if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
    49  		// The bytes package can use itself however it wants
    50  		return nil, nil
    51  	}
    52  	fn := func(node ast.Node, stack []ast.Node) {
    53  		m, ok := code.Match(pass, checkBytesBufferConversionsQ, node)
    54  		if !ok {
    55  			return
    56  		}
    57  		call := node.(*ast.CallExpr)
    58  		sel := m.State["sel"].(*ast.SelectorExpr)
    59  
    60  		typ := pass.TypesInfo.TypeOf(call.Fun)
    61  		if typ == types.Universe.Lookup("string").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).Bytes") {
    62  			if _, ok := stack[len(stack)-2].(*ast.IndexExpr); ok {
    63  				// Don't flag m[string(buf.Bytes())] – thanks to a
    64  				// compiler optimization, this is actually faster than
    65  				// m[buf.String()]
    66  				return
    67  			}
    68  
    69  			report.Report(pass, call, fmt.Sprintf("should use %v.String() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
    70  				report.FilterGenerated(),
    71  				report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRs, m.State))))
    72  		} else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).String") {
    73  			report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
    74  				report.FilterGenerated(),
    75  				report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRb, m.State))))
    76  		}
    77  
    78  	}
    79  	code.PreorderStack(pass, fn, (*ast.CallExpr)(nil))
    80  	return nil, nil
    81  }