package s1030 import ( "fmt" "go/ast" "go/types" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/edit" "honnef.co/go/tools/analysis/facts/generated" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/pattern" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "S1030", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `Use \'bytes.Buffer.String\' or \'bytes.Buffer.Bytes\'`, Text: `\'bytes.Buffer\' has both a \'String\' and a \'Bytes\' method. It is almost never necessary to use \'string(buf.Bytes())\' or \'[]byte(buf.String())\' – simply use the other method. The only exception to this are map lookups. Due to a compiler optimization, \'m[string(buf.Bytes())]\' is more efficient than \'m[buf.String()]\'. `, Since: "2017.1", MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer var ( checkBytesBufferConversionsQ = pattern.MustParse(`(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])`) checkBytesBufferConversionsRs = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "String")) [])`) checkBytesBufferConversionsRb = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "Bytes")) [])`) ) func run(pass *analysis.Pass) (interface{}, error) { if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" { // The bytes package can use itself however it wants return nil, nil } fn := func(node ast.Node, stack []ast.Node) { m, ok := code.Match(pass, checkBytesBufferConversionsQ, node) if !ok { return } call := node.(*ast.CallExpr) sel := m.State["sel"].(*ast.SelectorExpr) typ := pass.TypesInfo.TypeOf(call.Fun) if types.Unalias(typ) == types.Universe.Lookup("string").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).Bytes") { if _, ok := stack[len(stack)-2].(*ast.IndexExpr); ok { // Don't flag m[string(buf.Bytes())] – thanks to a // compiler optimization, this is actually faster than // m[buf.String()] return } report.Report(pass, call, fmt.Sprintf("should use %v.String() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)), report.FilterGenerated(), report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRs, m.State)))) } else if typ, ok := types.Unalias(typ).(*types.Slice); ok && types.Unalias(typ.Elem()) == types.Universe.Lookup("byte").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).String") { report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)), report.FilterGenerated(), report.Fixes(edit.Fix("simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRb, m.State)))) } } code.PreorderStack(pass, fn, (*ast.CallExpr)(nil)) return nil, nil }