github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1007/s1007.go (about) 1 package s1007 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/amarpal/go-tools/analysis/code" 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 "github.com/amarpal/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.Documentation{ 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 }