1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package main
import (
"go/ast"
"go/token"
"regexp"
"strings"
"golang.org/x/tools/go/analysis"
)
const quoteInterpolationAnalyzerName = "string_interpolation_quote"
type quoteInterpolationAnalyzerSettings struct {
IncludedFunctions []string `mapstructure:"included-functions"`
}
// newQuoteInterpolationAnalyzer returns an analyzer to detect manually quoted string interpolation
// with '%s' and "%s". Quoting this way doesn't escape special characters such as endline and makes
// debugging harder later. We encourage to use %q verb instead.
//
// - Bad
// return fmt.Errorf("gl_id='%s' is invalid", glID)
//
// - Bad
// fmt.Sprintf("fatal: not a git repository: '%s'", repoPath)
//
// - Good
// return fmt.Errorf("gl_id=%q is invalid", glID)
//
// - Good
// fmt.Sprintf("fatal: not a git repository: %q", repoPath)
//
// For more information:
// https://gitlab.com/gitlab-org/gitaly/-/blob/master/STYLE.md#use-q-when-interpolating-strings
func newQuoteInterpolationAnalyzer(settings *quoteInterpolationAnalyzerSettings) *analysis.Analyzer {
return &analysis.Analyzer{
Name: quoteInterpolationAnalyzerName,
Doc: `Unless it would lead to incorrect results, always use %q when
interpolating strings. For more information:
https://gitlab.com/gitlab-org/gitaly/-/blob/master/STYLE.md#use-q-when-interpolating-strings`,
Run: runStringInterpolationQuoteAnalyzer(settings.IncludedFunctions),
}
}
// offendedFormatPattern matches string interpolation having '%s' and "%s" format
var offendedFormatPattern = regexp.MustCompile(`['"]%s['"]`)
func analyzeInterpolation(call *ast.CallExpr, pass *analysis.Pass) {
if len(call.Args) <= 1 {
return
}
if str, ok := call.Args[0].(*ast.BasicLit); ok && str.Kind == token.STRING {
value := str.Value
if strings.HasPrefix(value, `'`) || strings.HasPrefix(value, `"`) {
value = value[1:]
}
if strings.HasSuffix(value, `'`) || strings.HasSuffix(value, `"`) {
value = value[:len(value)-1]
}
for _, index := range offendedFormatPattern.FindAllIndex([]byte(value), -1) {
start := token.Pos(int(str.Pos()) + index[0] + 1)
end := token.Pos(int(str.Pos()) + index[1])
pass.Report(analysis.Diagnostic{
Pos: start,
End: end,
Message: "wrapping %s verb with quotes is not encouraged, please use %q instead",
SuggestedFixes: nil,
})
}
}
}
func runStringInterpolationQuoteAnalyzer(rules []string) func(*analysis.Pass) (interface{}, error) {
return func(pass *analysis.Pass) (interface{}, error) {
matcher := NewMatcher(pass)
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
if matcher.MatchFunction(call, rules) {
analyzeInterpolation(call, pass)
}
}
return true
})
}
return nil, nil
}
}
|