s1004.go raw

   1  package s1004
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/token"
   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:     "S1004",
  22  		Run:      CheckBytesCompare,
  23  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
  24  	},
  25  	Doc: &lint.RawDocumentation{
  26  		Title:   `Replace call to \'bytes.Compare\' with \'bytes.Equal\'`,
  27  		Before:  `if bytes.Compare(x, y) == 0 {}`,
  28  		After:   `if bytes.Equal(x, y) {}`,
  29  		Since:   "2017.1",
  30  		MergeIf: lint.MergeIfAny,
  31  	},
  32  })
  33  
  34  var Analyzer = SCAnalyzer.Analyzer
  35  
  36  var (
  37  	checkBytesCompareQ  = pattern.MustParse(`(BinaryExpr (CallExpr (Symbol "bytes.Compare") args) op@(Or "==" "!=") (IntegerLiteral "0"))`)
  38  	checkBytesCompareRe = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args)`)
  39  	checkBytesCompareRn = pattern.MustParse(`(UnaryExpr "!" (CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args))`)
  40  )
  41  
  42  func CheckBytesCompare(pass *analysis.Pass) (interface{}, error) {
  43  	if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
  44  		// the bytes package is free to use bytes.Compare as it sees fit
  45  		return nil, nil
  46  	}
  47  	fn := func(node ast.Node) {
  48  		m, ok := code.Match(pass, checkBytesCompareQ, node)
  49  		if !ok {
  50  			return
  51  		}
  52  
  53  		args := report.RenderArgs(pass, m.State["args"].([]ast.Expr))
  54  		prefix := ""
  55  		if m.State["op"].(token.Token) == token.NEQ {
  56  			prefix = "!"
  57  		}
  58  
  59  		var fix analysis.SuggestedFix
  60  		switch tok := m.State["op"].(token.Token); tok {
  61  		case token.EQL:
  62  			fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRe, m.State))
  63  		case token.NEQ:
  64  			fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRn, m.State))
  65  		default:
  66  			panic(fmt.Sprintf("unexpected token %v", tok))
  67  		}
  68  		report.Report(pass, node, fmt.Sprintf("should use %sbytes.Equal(%s) instead", prefix, args), report.FilterGenerated(), report.Fixes(fix))
  69  	}
  70  	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
  71  	return nil, nil
  72  }
  73