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