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 }