src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa6005.go (2362B)


      1 package sa6005
      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/lint"
     11 	"honnef.co/go/tools/analysis/report"
     12 	"honnef.co/go/tools/pattern"
     13 
     14 	"golang.org/x/tools/go/analysis"
     15 )
     16 
     17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     18 	Analyzer: &analysis.Analyzer{
     19 		Name:     "SA6005",
     20 		Run:      run,
     21 		Requires: code.RequiredAnalyzers,
     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) (any, error) {
     62 	for node, m := range code.Matches(pass, checkToLowerToUpperComparisonQ) {
     63 		rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr)
     64 		method := "strings.EqualFold"
     65 		if m.State["tok"].(token.Token) == token.NEQ {
     66 			rn = &ast.UnaryExpr{
     67 				Op: token.NOT,
     68 				X:  rn,
     69 			}
     70 			method = "!" + method
     71 		}
     72 
     73 		report.Report(pass, node,
     74 			fmt.Sprintf("should use %s instead", method),
     75 			report.Fixes(edit.Fix("replace with "+method, edit.ReplaceWithNode(pass.Fset, node, rn))))
     76 	}
     77 	return nil, nil
     78 }