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 }