s1030.go raw

   1  package s1030
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/types"
   7  
   8  	"honnef.co/go/tools/analysis/code"
   9  	"honnef.co/go/tools/analysis/edit"
  10  	"honnef.co/go/tools/analysis/facts/generated"
  11  	"honnef.co/go/tools/analysis/lint"
  12  	"honnef.co/go/tools/analysis/report"
  13  	"honnef.co/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.RawDocumentation{
  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 types.Unalias(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 := types.Unalias(typ).(*types.Slice); ok &&
  73  			types.Unalias(typ.Elem()) == types.Universe.Lookup("byte").Type() &&
  74  			code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).String") {
  75  			report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
  76  				report.FilterGenerated(),
  77  				report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRb, m.State))))
  78  		}
  79  
  80  	}
  81  	code.PreorderStack(pass, fn, (*ast.CallExpr)(nil))
  82  	return nil, nil
  83  }
  84