github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1003/s1003.go (about) 1 package s1003 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 8 "github.com/amarpal/go-tools/analysis/code" 9 "github.com/amarpal/go-tools/analysis/edit" 10 "github.com/amarpal/go-tools/analysis/facts/generated" 11 "github.com/amarpal/go-tools/analysis/lint" 12 "github.com/amarpal/go-tools/analysis/report" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/passes/inspect" 16 ) 17 18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 19 Analyzer: &analysis.Analyzer{ 20 Name: "S1003", 21 Run: run, 22 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, 23 }, 24 Doc: &lint.Documentation{ 25 Title: `Replace call to \'strings.Index\' with \'strings.Contains\'`, 26 Before: `if strings.Index(x, y) != -1 {}`, 27 After: `if strings.Contains(x, y) {}`, 28 Since: "2017.1", 29 MergeIf: lint.MergeIfAny, 30 }, 31 }) 32 33 var Analyzer = SCAnalyzer.Analyzer 34 35 func run(pass *analysis.Pass) (interface{}, error) { 36 // map of value to token to bool value 37 allowed := map[int64]map[token.Token]bool{ 38 -1: {token.GTR: true, token.NEQ: true, token.EQL: false}, 39 0: {token.GEQ: true, token.LSS: false}, 40 } 41 fn := func(node ast.Node) { 42 expr := node.(*ast.BinaryExpr) 43 switch expr.Op { 44 case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL: 45 default: 46 return 47 } 48 49 value, ok := code.ExprToInt(pass, expr.Y) 50 if !ok { 51 return 52 } 53 54 allowedOps, ok := allowed[value] 55 if !ok { 56 return 57 } 58 b, ok := allowedOps[expr.Op] 59 if !ok { 60 return 61 } 62 63 call, ok := expr.X.(*ast.CallExpr) 64 if !ok { 65 return 66 } 67 sel, ok := call.Fun.(*ast.SelectorExpr) 68 if !ok { 69 return 70 } 71 pkgIdent, ok := sel.X.(*ast.Ident) 72 if !ok { 73 return 74 } 75 funIdent := sel.Sel 76 if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" { 77 return 78 } 79 80 var r ast.Expr 81 switch funIdent.Name { 82 case "IndexRune": 83 r = &ast.SelectorExpr{ 84 X: pkgIdent, 85 Sel: &ast.Ident{Name: "ContainsRune"}, 86 } 87 case "IndexAny": 88 r = &ast.SelectorExpr{ 89 X: pkgIdent, 90 Sel: &ast.Ident{Name: "ContainsAny"}, 91 } 92 case "Index": 93 r = &ast.SelectorExpr{ 94 X: pkgIdent, 95 Sel: &ast.Ident{Name: "Contains"}, 96 } 97 default: 98 return 99 } 100 101 r = &ast.CallExpr{ 102 Fun: r, 103 Args: call.Args, 104 } 105 if !b { 106 r = &ast.UnaryExpr{ 107 Op: token.NOT, 108 X: r, 109 } 110 } 111 112 report.Report(pass, node, fmt.Sprintf("should use %s instead", report.Render(pass, r)), 113 report.FilterGenerated(), 114 report.Fixes(edit.Fix(fmt.Sprintf("simplify use of %s", report.Render(pass, call.Fun)), edit.ReplaceWithNode(pass.Fset, node, r)))) 115 } 116 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) 117 return nil, nil 118 }