s1007.go raw
1 package s1007
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/token"
7 "strings"
8
9 "honnef.co/go/tools/analysis/code"
10 "honnef.co/go/tools/analysis/facts/generated"
11 "honnef.co/go/tools/analysis/lint"
12 "honnef.co/go/tools/analysis/report"
13 "honnef.co/go/tools/knowledge"
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: "S1007",
22 Run: run,
23 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
24 },
25 Doc: &lint.RawDocumentation{
26 Title: `Simplify regular expression by using raw string literal`,
27 Text: `Raw string literals use backticks instead of quotation marks and do not support
28 any escape sequences. This means that the backslash can be used
29 freely, without the need of escaping.
30
31 Since regular expressions have their own escape sequences, raw strings
32 can improve their readability.`,
33 Before: `regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")`,
34 After: "regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)",
35 Since: "2017.1",
36 MergeIf: lint.MergeIfAny,
37 },
38 })
39
40 var Analyzer = SCAnalyzer.Analyzer
41
42 func run(pass *analysis.Pass) (interface{}, error) {
43 fn := func(node ast.Node) {
44 call := node.(*ast.CallExpr)
45 if !code.IsCallToAny(pass, call, "regexp.MustCompile", "regexp.Compile") {
46 return
47 }
48 sel, ok := call.Fun.(*ast.SelectorExpr)
49 if !ok {
50 return
51 }
52 lit, ok := call.Args[knowledge.Arg("regexp.Compile.expr")].(*ast.BasicLit)
53 if !ok {
54 // TODO(dominikh): support string concat, maybe support constants
55 return
56 }
57 if lit.Kind != token.STRING {
58 // invalid function call
59 return
60 }
61 if lit.Value[0] != '"' {
62 // already a raw string
63 return
64 }
65 val := lit.Value
66 if !strings.Contains(val, `\\`) {
67 return
68 }
69 if strings.Contains(val, "`") {
70 return
71 }
72
73 bs := false
74 for _, c := range val {
75 if !bs && c == '\\' {
76 bs = true
77 continue
78 }
79 if bs && c == '\\' {
80 bs = false
81 continue
82 }
83 if bs {
84 // backslash followed by non-backslash -> escape sequence
85 return
86 }
87 }
88
89 report.Report(pass, call, fmt.Sprintf("should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name), report.FilterGenerated())
90 }
91 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
92 return nil, nil
93 }
94