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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
package st1005
import (
"go/constant"
"strings"
"unicode"
"unicode/utf8"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1005",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Incorrectly formatted error string`,
Text: `Error strings follow a set of guidelines to ensure uniformity and good
composability.
Quoting Go Code Review Comments:
> Error strings should not be capitalized (unless beginning with
> proper nouns or acronyms) or end with punctuation, since they are
> usually printed following other context. That is, use
> \'fmt.Errorf("something bad")\' not \'fmt.Errorf("Something bad")\', so
> that \'log.Printf("Reading %s: %v", filename, err)\' formats without a
> spurious capital letter mid-message.`,
Since: "2019.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (interface{}, error) {
objNames := map[*ir.Package]map[string]bool{}
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
objNames[irpkg] = map[string]bool{}
for _, m := range irpkg.Members {
if typ, ok := m.(*ir.Type); ok {
objNames[irpkg][typ.Name()] = true
}
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
objNames[fn.Package()][fn.Name()] = true
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
if code.IsInTest(pass, fn) {
// We don't care about malformed error messages in tests;
// they're usually for direct human consumption, not part
// of an API
continue
}
for _, block := range fn.Blocks {
instrLoop:
for _, ins := range block.Instrs {
call, ok := ins.(*ir.Call)
if !ok {
continue
}
if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
continue
}
k, ok := call.Common().Args[0].(*ir.Const)
if !ok {
continue
}
s := constant.StringVal(k.Value)
if len(s) == 0 {
continue
}
switch s[len(s)-1] {
case '.', ':', '!', '\n':
report.Report(pass, call, "error strings should not end with punctuation or newlines")
}
idx := strings.IndexByte(s, ' ')
if idx == -1 {
// single word error message, probably not a real
// error but something used in tests or during
// debugging
continue
}
word := s[:idx]
first, n := utf8.DecodeRuneInString(word)
if !unicode.IsUpper(first) {
continue
}
for _, c := range word[n:] {
if unicode.IsUpper(c) || unicode.IsDigit(c) {
// Word is probably an initialism or multi-word function name. Digits cover elliptic curves like
// P384.
continue instrLoop
}
}
if strings.ContainsRune(word, '(') {
// Might be a function call
continue instrLoop
}
word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
if objNames[fn.Package()][word] {
// Word is probably the name of a function or type in this package
continue
}
// First word in error starts with a capital
// letter, and the word doesn't contain any other
// capitals, making it unlikely to be an
// initialism or multi-word function name.
//
// It could still be a proper noun, though.
report.Report(pass, call, "error strings should not be capitalized")
}
}
}
return nil, nil
}
|