github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa6005/sa6005.go (about) 1 package sa6005 2 3 import ( 4 "go/ast" 5 "go/token" 6 7 "github.com/amarpal/go-tools/analysis/code" 8 "github.com/amarpal/go-tools/analysis/edit" 9 "github.com/amarpal/go-tools/analysis/lint" 10 "github.com/amarpal/go-tools/analysis/report" 11 "github.com/amarpal/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.Documentation{ 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 }