github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/quickfix/qf1004/qf1004.go (about)

     1  package qf1004
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/edit"
    10  	"github.com/amarpal/go-tools/analysis/lint"
    11  	"github.com/amarpal/go-tools/analysis/report"
    12  	"github.com/amarpal/go-tools/go/types/typeutil"
    13  	"github.com/amarpal/go-tools/pattern"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  )
    18  
    19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    20  	Analyzer: &analysis.Analyzer{
    21  		Name:     "QF1004",
    22  		Run:      run,
    23  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    24  	},
    25  	Doc: &lint.Documentation{
    26  		Title:    `Use \'strings.ReplaceAll\' instead of \'strings.Replace\' with \'n == -1\'`,
    27  		Since:    "2021.1",
    28  		Severity: lint.SeverityHint,
    29  	},
    30  })
    31  
    32  var Analyzer = SCAnalyzer.Analyzer
    33  
    34  var stringsReplaceAllQ = pattern.MustParse(`(Or
    35  	(CallExpr fn@(Symbol "strings.Replace") [_ _ _ lit@(IntegerLiteral "-1")])
    36  	(CallExpr fn@(Symbol "strings.SplitN") [_ _ lit@(IntegerLiteral "-1")])
    37  	(CallExpr fn@(Symbol "strings.SplitAfterN") [_ _ lit@(IntegerLiteral "-1")])
    38  	(CallExpr fn@(Symbol "bytes.Replace") [_ _ _ lit@(IntegerLiteral "-1")])
    39  	(CallExpr fn@(Symbol "bytes.SplitN") [_ _ lit@(IntegerLiteral "-1")])
    40  	(CallExpr fn@(Symbol "bytes.SplitAfterN") [_ _ lit@(IntegerLiteral "-1")]))`)
    41  
    42  func run(pass *analysis.Pass) (interface{}, error) {
    43  	// XXX respect minimum Go version
    44  
    45  	// FIXME(dh): create proper suggested fix for renamed import
    46  
    47  	fn := func(node ast.Node) {
    48  		matcher, ok := code.Match(pass, stringsReplaceAllQ, node)
    49  		if !ok {
    50  			return
    51  		}
    52  
    53  		var replacement string
    54  		switch typeutil.FuncName(matcher.State["fn"].(*types.Func)) {
    55  		case "strings.Replace":
    56  			replacement = "strings.ReplaceAll"
    57  		case "strings.SplitN":
    58  			replacement = "strings.Split"
    59  		case "strings.SplitAfterN":
    60  			replacement = "strings.SplitAfter"
    61  		case "bytes.Replace":
    62  			replacement = "bytes.ReplaceAll"
    63  		case "bytes.SplitN":
    64  			replacement = "bytes.Split"
    65  		case "bytes.SplitAfterN":
    66  			replacement = "bytes.SplitAfter"
    67  		default:
    68  			panic("unreachable")
    69  		}
    70  
    71  		call := node.(*ast.CallExpr)
    72  		report.Report(pass, call.Fun, fmt.Sprintf("could use %s instead", replacement),
    73  			report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead", replacement),
    74  				edit.ReplaceWithString(call.Fun, replacement),
    75  				edit.Delete(matcher.State["lit"].(ast.Node)))))
    76  	}
    77  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
    78  	return nil, nil
    79  }