File: st1005.go

package info (click to toggle)
golang-honnef-go-tools 2024.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,968 kB
  • sloc: sh: 132; xml: 48; lisp: 31; makefile: 11; javascript: 1
file content (130 lines) | stat: -rw-r--r-- 3,688 bytes parent folder | download
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
}