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