src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

s1030.go (3160B)


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