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