qf1004.go raw

   1  package qf1004
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/types"
   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/go/types/typeutil"
  13  	"honnef.co/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.RawDocumentation{
  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  }
  80