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  }