sa6005.go raw

   1  package sa6005
   2  
   3  import (
   4  	"go/ast"
   5  	"go/token"
   6  
   7  	"honnef.co/go/tools/analysis/code"
   8  	"honnef.co/go/tools/analysis/edit"
   9  	"honnef.co/go/tools/analysis/lint"
  10  	"honnef.co/go/tools/analysis/report"
  11  	"honnef.co/go/tools/pattern"
  12  
  13  	"golang.org/x/tools/go/analysis"
  14  	"golang.org/x/tools/go/analysis/passes/inspect"
  15  )
  16  
  17  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  18  	Analyzer: &analysis.Analyzer{
  19  		Name:     "SA6005",
  20  		Run:      run,
  21  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  22  	},
  23  	Doc: &lint.RawDocumentation{
  24  		Title: `Inefficient string comparison with \'strings.ToLower\' or \'strings.ToUpper\'`,
  25  		Text: `Converting two strings to the same case and comparing them like so
  26  
  27      if strings.ToLower(s1) == strings.ToLower(s2) {
  28          ...
  29      }
  30  
  31  is significantly more expensive than comparing them with
  32  \'strings.EqualFold(s1, s2)\'. This is due to memory usage as well as
  33  computational complexity.
  34  
  35  \'strings.ToLower\' will have to allocate memory for the new strings, as
  36  well as convert both strings fully, even if they differ on the very
  37  first byte. strings.EqualFold, on the other hand, compares the strings
  38  one character at a time. It doesn't need to create two intermediate
  39  strings and can return as soon as the first non-matching character has
  40  been found.
  41  
  42  For a more in-depth explanation of this issue, see
  43  https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/`,
  44  		Since:    "2019.2",
  45  		Severity: lint.SeverityWarning,
  46  		MergeIf:  lint.MergeIfAny,
  47  	},
  48  })
  49  
  50  var Analyzer = SCAnalyzer.Analyzer
  51  
  52  var (
  53  	checkToLowerToUpperComparisonQ = pattern.MustParse(`
  54  	(BinaryExpr
  55  		(CallExpr fun@(Symbol (Or "strings.ToLower" "strings.ToUpper")) [a])
  56   		tok@(Or "==" "!=")
  57   		(CallExpr fun [b]))`)
  58  	checkToLowerToUpperComparisonR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "strings") (Ident "EqualFold")) [a b])`)
  59  )
  60  
  61  func run(pass *analysis.Pass) (interface{}, error) {
  62  	fn := func(node ast.Node) {
  63  		m, ok := code.Match(pass, checkToLowerToUpperComparisonQ, node)
  64  		if !ok {
  65  			return
  66  		}
  67  		rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr)
  68  		if m.State["tok"].(token.Token) == token.NEQ {
  69  			rn = &ast.UnaryExpr{
  70  				Op: token.NOT,
  71  				X:  rn,
  72  			}
  73  		}
  74  
  75  		report.Report(pass, node, "should use strings.EqualFold instead", report.Fixes(edit.Fix("replace with strings.EqualFold", edit.ReplaceWithNode(pass.Fset, node, rn))))
  76  	}
  77  
  78  	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
  79  	return nil, nil
  80  }
  81