├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── README.md
├── Taskfile.yml
├── go.mod
├── go.sum
├── main.go
└── pkg
└── analyzer
├── analyzer.go
├── analyzer_test.go
├── facts.go
├── facts_test.go
└── testdata
└── src
├── regular
├── error_types.go
├── error_types_arrays.go
├── sentinel.go
├── sentinel_from_constants.go
├── sentinel_from_funcs.go
├── sentinel_from_pkg_alias.go
└── sentinel_from_types.go
└── unusual
├── errortype
└── user_error_type.go
├── generics
├── generic_types.go
└── issue24.go
└── newfunc
└── user_new_func.go
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "monthly"
8 | ignore:
9 | - dependency-name: "*"
10 | update-types:
11 | - "version-update:semver-minor"
12 | - "version-update:semver-patch"
13 |
14 | - package-ecosystem: "gomod"
15 | directory: "/"
16 | schedule:
17 | interval: "monthly"
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | tags: [ v* ]
7 | pull_request:
8 | branches: [ master ]
9 |
10 | env:
11 | GO_VERSION: ^1.24
12 | GOLANGCI_LINT_VERSION: v1.64.7
13 |
14 | permissions:
15 | contents: read
16 |
17 | jobs:
18 | go_install:
19 | if: ${{ !github.event.pull_request.head.repo.fork }}
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: rlespinasse/github-slug-action@v5.0.0
23 | - uses: actions/setup-go@v5
24 | with:
25 | go-version: ${{ env.GO_VERSION }}
26 | - run: cd /tmp && go install github.com/Antonboom/errname@${{ env.version }} && errname -h
27 | env:
28 | version: ${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA || env.GITHUB_REF_SLUG }}
29 |
30 | lint:
31 | permissions:
32 | contents: read # for actions/checkout to fetch code
33 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v4
37 | - uses: actions/setup-go@v5
38 | with:
39 | go-version: ${{ env.GO_VERSION }}
40 | - uses: golangci/golangci-lint-action@v6
41 | with:
42 | version: ${{ env.GOLANGCI_LINT_VERSION }}
43 | args: --timeout=5m
44 |
45 | test:
46 | permissions:
47 | checks: write # for shogo82148/actions-goveralls to create a new check based on the results
48 | contents: read # for actions/checkout to fetch code
49 | runs-on: ubuntu-latest
50 | steps:
51 | - uses: actions/checkout@v4
52 | - uses: actions/setup-go@v5
53 | with:
54 | go-version: ${{ env.GO_VERSION }}
55 | - run: go test -coverprofile=coverage.out ./...
56 | - uses: shogo82148/actions-goveralls@v1
57 | with:
58 | path-to-profile: coverage.out
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | .DS_Store
4 | coverage.out
5 | *.mem
6 | *.cpu
7 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | run:
2 | tests: true
3 |
4 | issues:
5 | max-same-issues: 0
6 |
7 | linters-settings:
8 | gci:
9 | sections:
10 | - standard
11 | - default
12 | - prefix(github.com/Antonboom/errname)
13 |
14 | linters:
15 | disable-all: true
16 | enable:
17 | - asasalint
18 | - asciicheck
19 | - bidichk
20 | - bodyclose
21 | - copyloopvar
22 | - dogsled
23 | - dupl
24 | - durationcheck
25 | - errcheck
26 | - errchkjson
27 | - errname
28 | - exhaustive
29 | - exptostd
30 | - forbidigo
31 | - gci
32 | - gocheckcompilerdirectives
33 | - gocognit
34 | - goconst
35 | - gocritic
36 | - gocyclo
37 | - godot
38 | - godox
39 | - gofmt
40 | - gofumpt
41 | - goheader
42 | - goimports
43 | - gomoddirectives
44 | - gomodguard
45 | - goprintffuncname
46 | - gosec
47 | - gosimple
48 | - govet
49 | - importas
50 | - ineffassign
51 | - lll
52 | - makezero
53 | - misspell
54 | - mirror
55 | - mnd
56 | - nakedret
57 | - nilerr
58 | - nilnesserr
59 | - nilnil
60 | - nestif
61 | - noctx
62 | - nolintlint
63 | - nosprintfhostport
64 | - prealloc
65 | - predeclared
66 | - reassign
67 | - revive
68 | - sqlclosecheck
69 | - staticcheck
70 | - stylecheck
71 | - tagliatelle
72 | - testableexamples
73 | - thelper
74 | - tparallel
75 | - typecheck
76 | - unconvert
77 | - unparam
78 | - unused
79 | - usestdlibvars
80 | - usetesting
81 | - wastedassign
82 | - whitespace
83 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Anton Telyshev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # errname
2 |
3 | 
4 | [](https://github.com/Antonboom/errname/actions/workflows/ci.yml)
5 | [](https://goreportcard.com/report/github.com/Antonboom/errname)
6 | [](https://coveralls.io/github/Antonboom/errname?branch=master)
7 | [](LICENSE)
8 |
9 | Checks that sentinel errors are prefixed with the `Err` and error types
10 | are suffixed with the `Error` or `Errors`.
11 |
12 | ## Installation & usage
13 |
14 | ```
15 | $ go install github.com/Antonboom/errname@latest
16 | $ errname ./...
17 | ```
18 |
19 | ## Motivation
20 |
21 | [The convention](https://go.dev/wiki/Errors#naming) states that
22 | > Error types end in "Error" and error variables start with "Err" or "err".
23 |
24 | This can be found in the standard Go library:
25 |
26 | ```go
27 | type AddrError struct{ /* ... */ }
28 | type DecodeError struct{ /*...*/ }
29 | type NoMatchingVersionError struct{ /*...*/ }
30 | type nothingWrittenError struct{ /*...*/ }
31 | type pasteIndicatorError struct{ /*...*/ }
32 | type wrapError struct{ /*...*/ }
33 |
34 | var ErrFinalToken = errors.New("final token")
35 | var ErrRange = errors.New("value out of range")
36 | var ErrUnsupportedAlgorithm = errors.New("x509: cannot verify signature: algorithm unimplemented")
37 | var errMissing = errors.New("cannot find package")
38 | var errNUL = errors.New("unexpected NUL in input")
39 | var errTagValueSpace = errors.New("suspicious space in struct tag value")
40 | ```
41 |
42 | Also, this can be found in some articles about errors in Go, for
43 | example, [here](https://travix.io/errors-derived-from-constants-in-go-fda6748b4072):
44 | > At first sight you’d think the naming is a bit weird, but that is because of conventions: types are suffixed with
45 | > “Error”, while constants are prefixed with “Err”. So we’re just following the conventions here.
46 |
47 | This is a good rule to improve the consistency of your code. **More uniformity, what could be better?**
48 |
49 | ### What if I think it's bullshit?
50 |
51 | Just don't enable the linter.
52 |
53 | ### Why not [revive/error-naming](https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming)?
54 |
55 | At the time of writing this linter, I was unaware of **revive**.
56 | **errname** performs more complex and better checks anyway.
57 |
58 | ## Examples
59 |
60 | ```go
61 | // Bad.
62 | type DecodeErr struct{} // the error type name `DecodeErr` should conform to the `xxxError` format
63 | func (d *DecodeErr) Error() string { /*...*/ }
64 |
65 | // Good.
66 | type DecodeError struct{}
67 | func (d *DecodeError) Error() string { /*...*/ }
68 | ```
69 |
70 | ```go
71 | // Bad.
72 | var InvalidURLErr = errors.New("invalid url") // the sentinel error name `InvalidURLErr` should conform to the `ErrXxx` format
73 |
74 | // Good.
75 | var ErrInvalidURL = errors.New("invalid url") // or errInvalidURL
76 | ```
77 |
78 | More examples in [tests](https://github.com/Antonboom/errname/blob/master/pkg/analyzer/facts_test.go).
79 |
80 | ## Assumptions
81 |
82 |
83 | Click to expand
84 |
85 |
86 |
87 | - Only package level variables (and constants) are checked.
88 | - Initialisms are ignored. As a result, all identifiers in a single case are ignored:
89 |
90 | ```go
91 | var EOF = errors.New("end of file")
92 | var eof = errors.New("end of file")
93 | var W = errors.New("single letter error") // on the developer's conscience
94 | var ovflw = errors.New("value is overflow") // on the developer's conscience
95 | ```
96 |
97 | - The naming of error constructors is not checked. But I recommend keeping it consistent:
98 |
99 | ```go
100 | type DecodeError struct{}
101 | func (d *DecodeError) Error() string { /*...*/ }
102 |
103 | // Bad.
104 | func NewErrDecode() error {
105 | return &DecodeError{}
106 | }
107 |
108 | // Good.
109 | func NewDecodeError() error {
110 | return &DecodeError{}
111 | }
112 | ```
113 |
114 | - Linter only checks the correctness of the suffix and prefix and their **uniqueness**. The logical meaning of the
115 | identifier remains on the developer's conscience:
116 |
117 | ```go
118 | // Bad.
119 | var ErrExecErr = errors.New("exec query error")
120 |
121 | // Good.
122 | var ErrExecQuery = errors.New("exec query error")
123 | var ErrGdfjnskjdfskf = errors.New("strange error") // on the developer's conscience
124 | ```
125 |
126 | - For error types over array/slice the `Errors` suffix is also allowed:
127 |
128 | ```go
129 | // Bad.
130 | type ValidationErrs []string
131 | func (ve ValidationErrs) Error() string { /*...*/ }
132 |
133 | // Good.
134 | type ValidationErrors []string
135 | func (ve ValidationErrors) Error() string { /*...*/ }
136 | ```
137 |
138 |
139 |
140 | ## Large projects examples
141 |
142 |
143 | Golang source code
144 |
145 | ```go
146 | $ errname ./src/...
147 | go/src/runtime/error.go:72:6: the error type name `errorString` should conform to the `xxxError` format
148 | go/src/runtime/error.go:80:6: the error type name `errorAddressString` should conform to the `xxxError` format
149 | go/src/runtime/panic.go:180:5: the sentinel error name `shiftError` should conform to the `errXxx` format
150 | go/src/runtime/panic.go:187:5: the sentinel error name `divideError` should conform to the `errXxx` format
151 | go/src/runtime/panic.go:194:5: the sentinel error name `overflowError` should conform to the `errXxx` format
152 | go/src/runtime/panic.go:201:5: the sentinel error name `floatError` should conform to the `errXxx` format
153 | go/src/runtime/panic.go:208:5: the sentinel error name `memoryError` should conform to the `errXxx` format
154 | go/src/errors/errors.go:63:6: the error type name `errorString` should conform to the `xxxError` format
155 | go/src/math/bits/bits_errors.go:12:5: the sentinel error name `overflowError` should conform to the `errXxx` format
156 | go/src/math/bits/bits_errors.go:15:5: the sentinel error name `divideError` should conform to the `errXxx` format
157 | go/src/syscall/syscall_unix.go:114:6: the error type name `Errno` should conform to the `XxxError` format
158 | go/src/time/format.go:394:5: the sentinel error name `atoiError` should conform to the `errXxx` format
159 | go/src/time/zoneinfo_read.go:110:5: the sentinel error name `badData` should conform to the `errXxx` format
160 | go/src/io/fs/walk.go:15:5: the sentinel error name `SkipDir` should conform to the `ErrXxx` format
161 | go/src/fmt/scan.go:465:5: the sentinel error name `complexError` should conform to the `errXxx` format
162 | go/src/fmt/scan.go:466:5: the sentinel error name `boolError` should conform to the `errXxx` format
163 | go/src/archive/tar/common.go:39:6: the error type name `headerError` should conform to the `xxxError` format
164 | go/src/context/context.go:157:5: the sentinel error name `Canceled` should conform to the `ErrXxx` format
165 | go/src/context/context.go:161:5: the sentinel error name `DeadlineExceeded` should conform to the `ErrXxx` format
166 | go/src/math/big/float.go:77:6: the error type name `ErrNaN` should conform to the `XxxError` format
167 | go/src/crypto/x509/internal/macos/security.go:39:6: the error type name `OSStatus` should conform to the `XxxError` format
168 | go/src/net/cgo_unix.go:34:6: the error type name `addrinfoErrno` should conform to the `xxxError` format
169 | go/src/crypto/x509/x509.go:875:6: the error type name `UnhandledCriticalExtension` should conform to the `XxxError` format
170 | go/src/crypto/x509/pem_decrypt.go:110:5: the sentinel error name `IncorrectPasswordError` should conform to the `ErrXxx` format
171 | go/src/crypto/x509/root.go:18:2: the sentinel error name `systemRootsErr` should conform to the `errXxx` format
172 | go/src/crypto/tls/alert.go:18:2: the sentinel error name `alertCloseNotify` should conform to the `errXxx` format
173 | go/src/crypto/tls/alert.go:19:2: the sentinel error name `alertUnexpectedMessage` should conform to the `errXxx` format
174 | go/src/crypto/tls/alert.go:20:2: the sentinel error name `alertBadRecordMAC` should conform to the `errXxx` format
175 | go/src/crypto/tls/alert.go:21:2: the sentinel error name `alertDecryptionFailed` should conform to the `errXxx` format
176 | go/src/crypto/tls/alert.go:22:2: the sentinel error name `alertRecordOverflow` should conform to the `errXxx` format
177 | go/src/crypto/tls/alert.go:23:2: the sentinel error name `alertDecompressionFailure` should conform to the `errXxx` format
178 | go/src/crypto/tls/alert.go:24:2: the sentinel error name `alertHandshakeFailure` should conform to the `errXxx` format
179 | go/src/crypto/tls/alert.go:25:2: the sentinel error name `alertBadCertificate` should conform to the `errXxx` format
180 | go/src/crypto/tls/alert.go:26:2: the sentinel error name `alertUnsupportedCertificate` should conform to the `errXxx` format
181 | go/src/crypto/tls/alert.go:27:2: the sentinel error name `alertCertificateRevoked` should conform to the `errXxx` format
182 | go/src/crypto/tls/alert.go:28:2: the sentinel error name `alertCertificateExpired` should conform to the `errXxx` format
183 | go/src/crypto/tls/alert.go:29:2: the sentinel error name `alertCertificateUnknown` should conform to the `errXxx` format
184 | go/src/crypto/tls/alert.go:30:2: the sentinel error name `alertIllegalParameter` should conform to the `errXxx` format
185 | go/src/crypto/tls/alert.go:31:2: the sentinel error name `alertUnknownCA` should conform to the `errXxx` format
186 | go/src/crypto/tls/alert.go:32:2: the sentinel error name `alertAccessDenied` should conform to the `errXxx` format
187 | go/src/crypto/tls/alert.go:33:2: the sentinel error name `alertDecodeError` should conform to the `errXxx` format
188 | go/src/crypto/tls/alert.go:34:2: the sentinel error name `alertDecryptError` should conform to the `errXxx` format
189 | go/src/crypto/tls/alert.go:35:2: the sentinel error name `alertExportRestriction` should conform to the `errXxx` format
190 | go/src/crypto/tls/alert.go:36:2: the sentinel error name `alertProtocolVersion` should conform to the `errXxx` format
191 | go/src/crypto/tls/alert.go:37:2: the sentinel error name `alertInsufficientSecurity` should conform to the `errXxx` format
192 | go/src/crypto/tls/alert.go:38:2: the sentinel error name `alertInternalError` should conform to the `errXxx` format
193 | go/src/crypto/tls/alert.go:39:2: the sentinel error name `alertInappropriateFallback` should conform to the `errXxx` format
194 | go/src/crypto/tls/alert.go:40:2: the sentinel error name `alertUserCanceled` should conform to the `errXxx` format
195 | go/src/crypto/tls/alert.go:41:2: the sentinel error name `alertNoRenegotiation` should conform to the `errXxx` format
196 | go/src/crypto/tls/alert.go:42:2: the sentinel error name `alertMissingExtension` should conform to the `errXxx` format
197 | go/src/crypto/tls/alert.go:43:2: the sentinel error name `alertUnsupportedExtension` should conform to the `errXxx` format
198 | go/src/crypto/tls/alert.go:44:2: the sentinel error name `alertCertificateUnobtainable` should conform to the `errXxx` format
199 | go/src/crypto/tls/alert.go:45:2: the sentinel error name `alertUnrecognizedName` should conform to the `errXxx` format
200 | go/src/crypto/tls/alert.go:46:2: the sentinel error name `alertBadCertificateStatusResponse` should conform to the `errXxx` format
201 | go/src/crypto/tls/alert.go:47:2: the sentinel error name `alertBadCertificateHashValue` should conform to the `errXxx` format
202 | go/src/crypto/tls/alert.go:48:2: the sentinel error name `alertUnknownPSKIdentity` should conform to the `errXxx` format
203 | go/src/crypto/tls/alert.go:49:2: the sentinel error name `alertCertificateRequired` should conform to the `errXxx` format
204 | go/src/crypto/tls/alert.go:50:2: the sentinel error name `alertNoApplicationProtocol` should conform to the `errXxx` format
205 | go/src/path/filepath/path.go:337:5: the sentinel error name `SkipDir` should conform to the `ErrXxx` format
206 | go/src/net/http/h2_bundle.go:1016:5: the sentinel error name `http2errReadEmpty` should conform to the `errXxx` format
207 | go/src/net/http/h2_bundle.go:1212:2: the sentinel error name `http2errMixPseudoHeaderTypes` should conform to the `errXxx` format
208 | go/src/net/http/h2_bundle.go:1213:2: the sentinel error name `http2errPseudoAfterRegular` should conform to the `errXxx` format
209 | go/src/net/http/h2_bundle.go:1712:5: the sentinel error name `http2ErrFrameTooLarge` should conform to the `errXxx` format
210 | go/src/net/http/h2_bundle.go:1866:2: the sentinel error name `http2errStreamID` should conform to the `errXxx` format
211 | go/src/net/http/h2_bundle.go:1867:2: the sentinel error name `http2errDepStreamID` should conform to the `errXxx` format
212 | go/src/net/http/h2_bundle.go:1868:2: the sentinel error name `http2errPadLength` should conform to the `errXxx` format
213 | go/src/net/http/h2_bundle.go:1869:2: the sentinel error name `http2errPadBytes` should conform to the `errXxx` format
214 | go/src/net/http/h2_bundle.go:3400:5: the sentinel error name `http2errTimeout` should conform to the `errXxx` format
215 | go/src/net/http/h2_bundle.go:3519:5: the sentinel error name `http2errClosedPipeWrite` should conform to the `errXxx` format
216 | go/src/net/http/h2_bundle.go:3629:2: the sentinel error name `http2errClientDisconnected` should conform to the `errXxx` format
217 | go/src/net/http/h2_bundle.go:3630:2: the sentinel error name `http2errClosedBody` should conform to the `errXxx` format
218 | go/src/net/http/h2_bundle.go:3631:2: the sentinel error name `http2errHandlerComplete` should conform to the `errXxx` format
219 | go/src/net/http/h2_bundle.go:3632:2: the sentinel error name `http2errStreamClosed` should conform to the `errXxx` format
220 | go/src/net/http/h2_bundle.go:4526:5: the sentinel error name `http2errPrefaceTimeout` should conform to the `errXxx` format
221 | go/src/net/http/h2_bundle.go:4746:5: the sentinel error name `http2errHandlerPanicked` should conform to the `errXxx` format
222 | go/src/net/http/h2_bundle.go:6287:2: the sentinel error name `http2ErrRecursivePush` should conform to the `errXxx` format
223 | go/src/net/http/h2_bundle.go:6288:2: the sentinel error name `http2ErrPushLimitReached` should conform to the `errXxx` format
224 | go/src/net/http/h2_bundle.go:6930:5: the sentinel error name `http2ErrNoCachedConn` should conform to the `errXxx` format
225 | go/src/net/http/h2_bundle.go:7016:2: the sentinel error name `http2errClientConnClosed` should conform to the `errXxx` format
226 | go/src/net/http/h2_bundle.go:7017:2: the sentinel error name `http2errClientConnUnusable` should conform to the `errXxx` format
227 | go/src/net/http/h2_bundle.go:7018:2: the sentinel error name `http2errClientConnGotGoAway` should conform to the `errXxx` format
228 | go/src/net/http/h2_bundle.go:7471:5: the sentinel error name `http2errRequestCanceled` should conform to the `errXxx` format
229 | go/src/net/http/h2_bundle.go:7803:2: the sentinel error name `http2errStopReqBodyWrite` should conform to the `errXxx` format
230 | go/src/net/http/h2_bundle.go:7806:2: the sentinel error name `http2errStopReqBodyWriteAndCancel` should conform to the `errXxx` format
231 | go/src/net/http/h2_bundle.go:7808:2: the sentinel error name `http2errReqBodyTooLong` should conform to the `errXxx` format
232 | go/src/net/http/h2_bundle.go:8667:5: the sentinel error name `http2errClosedResponseBody` should conform to the `errXxx` format
233 | go/src/net/http/h2_bundle.go:9021:2: the sentinel error name `http2errResponseHeaderListSize` should conform to the `errXxx` format
234 | go/src/net/http/h2_bundle.go:9022:2: the sentinel error name `http2errRequestHeaderListSize` should conform to the `errXxx` format
235 | go/src/go/scanner/errors.go:37:6: the error type name `ErrorList` should conform to the `XxxError` format
236 | go/src/html/template/template.go:34:5: the sentinel error name `escapeOK` should conform to the `errXxx` format
237 | go/src/image/png/reader.go:128:5: the sentinel error name `chunkOrderError` should conform to the `errXxx` format
238 | go/src/bufio/scan_test.go:308:5: the sentinel error name `testError` should conform to the `errXxx` format
239 | go/src/crypto/tls/handshake_client_test.go:1993:5: the sentinel error name `brokenConnErr` should conform to the `errXxx` format
240 | go/src/database/sql/sql_test.go:4281:5: the sentinel error name `pingError` should conform to the `errXxx` format
241 | go/src/errors/wrap_test.go:216:6: the error type name `errorT` should conform to the `xxxError` format
242 | go/src/errors/wrap_test.go:229:6: the error type name `errorUncomparable` should conform to the `xxxError` format
243 | go/src/fmt/errors_test.go:75:6: the error type name `errString` should conform to the `xxxError` format
244 | go/src/html/template/exec_test.go:233:5: the sentinel error name `myError` should conform to the `errXxx` format
245 | go/src/html/template/exec_test.go:1313:5: the sentinel error name `alwaysError` should conform to the `errXxx` format
246 | go/src/net/http/transport_test.go:6280:5: the sentinel error name `timeoutProtoErr` should conform to the `errXxx` format
247 | go/src/text/template/exec_test.go:229:5: the sentinel error name `myError` should conform to the `errXxx` format
248 | go/src/text/template/exec_test.go:1305:5: the sentinel error name `alwaysError` should conform to the `errXxx` format
249 | ```
250 |
251 |
252 |
253 |
254 | Traefik
255 |
256 | ```go
257 | $ errname./...
258 | # no issues
259 | ```
260 |
261 |
262 |
263 |
264 | Terraform
265 |
266 | ```go
267 | $ errname./...
268 | terraform/internal/getmodules/file_detector.go:59:6: the error type name `MaybeRelativePathErr` should conform to the `XxxError` format
269 | terraform/internal/getproviders/errors.go:13:6: the error type name `ErrHostNoProviders` should conform to the `XxxError` format
270 | terraform/internal/getproviders/errors.go:39:6: the error type name `ErrHostUnreachable` should conform to the `XxxError` format
271 | terraform/internal/getproviders/errors.go:57:6: the error type name `ErrUnauthorized` should conform to the `XxxError` format
272 | terraform/internal/getproviders/errors.go:80:6: the error type name `ErrProviderNotFound` should conform to the `XxxError` format
273 | terraform/internal/getproviders/errors.go:104:6: the error type name `ErrRegistryProviderNotKnown` should conform to the `XxxError` format
274 | terraform/internal/getproviders/errors.go:123:6: the error type name `ErrPlatformNotSupported` should conform to the `XxxError` format
275 | terraform/internal/getproviders/errors.go:159:6: the error type name `ErrProtocolNotSupported` should conform to the `XxxError` format
276 | terraform/internal/getproviders/errors.go:181:6: the error type name `ErrQueryFailed` should conform to the `XxxError` format
277 | terraform/internal/getproviders/errors.go:219:6: the error type name `ErrRequestCanceled` should conform to the `XxxError` format
278 | terraform/internal/registry/errors.go:10:6: the error type name `errModuleNotFound` should conform to the `xxxError` format
279 | terraform/internal/backend/remote-state/consul/client.go:36:5: the sentinel error name `lostLockErr` should conform to the `errXxx` format
280 | terraform/internal/command/cliconfig/credentials.go:408:6: the error type name `ErrUnwritableHostCredentials` should conform to the `XxxError` format
281 | ```
282 |
283 |
284 |
--------------------------------------------------------------------------------
/Taskfile.yml:
--------------------------------------------------------------------------------
1 | # https://taskfile.dev/#/installation
2 | version: '3'
3 |
4 | silent: true
5 |
6 | tasks:
7 | default:
8 | cmds:
9 | - task: tools:install
10 | - task: tidy
11 | - task: fmt
12 | - task: lint
13 | - task: test
14 | - task: install
15 |
16 | tools:install:
17 | - echo "Install local tools..."
18 | - (which gci > /dev/null) || go install github.com/daixiang0/gci@latest
19 | - (which gofumpt > /dev/null) || go install mvdan.cc/gofumpt@latest
20 |
21 | tidy:
22 | cmds:
23 | - echo "Tidy..."
24 | - go mod tidy
25 |
26 | fmt:
27 | cmds:
28 | - echo "Fmt..."
29 | - gofumpt -w .
30 | - gci write -s standard -s default -s "Prefix(github.com/Antonboom/errname)" . 2> /dev/null
31 |
32 | lint:
33 | cmds:
34 | - echo "Lint..."
35 | - golangci-lint run --fix ./...
36 |
37 | test:
38 | cmds:
39 | - echo "Tests..."
40 | - go test ./...
41 |
42 | test:coverage:
43 | cmds:
44 | - echo "Tests with coverage..."
45 | - go test -coverprofile=coverage.out ./...
46 |
47 | install:
48 | cmds:
49 | - echo "Install..."
50 | - go install ./...
51 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Antonboom/errname
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require golang.org/x/tools v0.32.0
8 |
9 | require (
10 | golang.org/x/mod v0.24.0 // indirect
11 | golang.org/x/sync v0.13.0 // indirect
12 | )
13 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
4 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
5 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
6 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
7 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
8 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
9 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "golang.org/x/tools/go/analysis/singlechecker"
5 |
6 | "github.com/Antonboom/errname/pkg/analyzer"
7 | )
8 |
9 | func main() {
10 | singlechecker.Main(analyzer.New())
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/analyzer/analyzer.go:
--------------------------------------------------------------------------------
1 | package analyzer
2 |
3 | import (
4 | "go/ast"
5 | "go/token"
6 | "go/types"
7 | "unicode"
8 |
9 | "golang.org/x/tools/go/analysis"
10 | "golang.org/x/tools/go/analysis/passes/inspect"
11 | "golang.org/x/tools/go/ast/inspector"
12 | )
13 |
14 | // New returns new errname analyzer.
15 | func New() *analysis.Analyzer {
16 | return &analysis.Analyzer{
17 | Name: "errname",
18 | Doc: "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.",
19 | Run: run,
20 | Requires: []*analysis.Analyzer{inspect.Analyzer},
21 | }
22 | }
23 |
24 | func run(pass *analysis.Pass) (interface{}, error) {
25 | insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
26 |
27 | insp.Nodes([]ast.Node{
28 | (*ast.TypeSpec)(nil),
29 | (*ast.ValueSpec)(nil),
30 | (*ast.FuncDecl)(nil),
31 | }, func(node ast.Node, push bool) bool {
32 | if !push {
33 | return false
34 | }
35 |
36 | switch v := node.(type) {
37 | case *ast.FuncDecl:
38 | return false
39 |
40 | case *ast.ValueSpec:
41 | if len(v.Names) != 1 {
42 | return false
43 | }
44 | ident := v.Names[0]
45 |
46 | if exprImplementsError(pass, ident) && !isValidErrorVarName(ident.Name) {
47 | reportAboutSentinelError(pass, v.Pos(), ident.Name)
48 | }
49 | return false
50 |
51 | case *ast.TypeSpec:
52 | tt := pass.TypesInfo.TypeOf(v.Name)
53 | if tt == nil {
54 | return false
55 | }
56 | // NOTE(a.telyshev): Pointer is the hack against Error() method with pointer receiver.
57 | if !typeImplementsError(types.NewPointer(tt)) {
58 | return false
59 | }
60 |
61 | name := v.Name.Name
62 | if _, ok := v.Type.(*ast.ArrayType); ok {
63 | if !isValidErrorArrayTypeName(name) {
64 | reportAboutArrayErrorType(pass, v.Pos(), name)
65 | }
66 | } else if !isValidErrorTypeName(name) {
67 | reportAboutErrorType(pass, v.Pos(), name)
68 | }
69 | return false
70 | }
71 |
72 | return true
73 | })
74 |
75 | return nil, nil //nolint:nilnil
76 | }
77 |
78 | func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName string) {
79 | var form string
80 | if unicode.IsLower([]rune(typeName)[0]) {
81 | form = "xxxError"
82 | } else {
83 | form = "XxxError"
84 | }
85 |
86 | pass.Reportf(typePos, "the error type name `%s` should conform to the `%s` format", typeName, form)
87 | }
88 |
89 | func reportAboutArrayErrorType(pass *analysis.Pass, typePos token.Pos, typeName string) {
90 | var forms string
91 | if unicode.IsLower([]rune(typeName)[0]) {
92 | forms = "`xxxErrors` or `xxxError`"
93 | } else {
94 | forms = "`XxxErrors` or `XxxError`"
95 | }
96 |
97 | pass.Reportf(typePos, "the error type name `%s` should conform to the %s format", typeName, forms)
98 | }
99 |
100 | func reportAboutSentinelError(pass *analysis.Pass, pos token.Pos, varName string) {
101 | var form string
102 | if unicode.IsLower([]rune(varName)[0]) {
103 | form = "errXxx"
104 | } else {
105 | form = "ErrXxx"
106 | }
107 | pass.Reportf(pos, "the sentinel error name `%s` should conform to the `%s` format", varName, form)
108 | }
109 |
--------------------------------------------------------------------------------
/pkg/analyzer/analyzer_test.go:
--------------------------------------------------------------------------------
1 | package analyzer
2 |
3 | import (
4 | "testing"
5 |
6 | "golang.org/x/tools/go/analysis/analysistest"
7 | )
8 |
9 | func TestErrName(t *testing.T) {
10 | pkgs := []string{
11 | "regular",
12 | "unusual/errortype",
13 | "unusual/generics",
14 | "unusual/newfunc",
15 | }
16 | analysistest.Run(t, analysistest.TestData(), New(), pkgs...)
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/analyzer/facts.go:
--------------------------------------------------------------------------------
1 | package analyzer
2 |
3 | import (
4 | "go/ast"
5 | "go/types"
6 | "strings"
7 | "unicode"
8 |
9 | "golang.org/x/tools/go/analysis"
10 | )
11 |
12 | var errorIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
13 |
14 | func exprImplementsError(pass *analysis.Pass, e ast.Expr) bool {
15 | return typeImplementsError(pass.TypesInfo.TypeOf(e))
16 | }
17 |
18 | func typeImplementsError(t types.Type) bool {
19 | return t != nil && types.Implements(t, errorIface)
20 | }
21 |
22 | func isValidErrorTypeName(s string) bool {
23 | if isInitialism(s) {
24 | return true
25 | }
26 |
27 | words := split(s)
28 | wordsCnt := wordsCount(words)
29 |
30 | if wordsCnt["error"] != 1 {
31 | return false
32 | }
33 | return words[len(words)-1] == "error"
34 | }
35 |
36 | func isValidErrorArrayTypeName(s string) bool {
37 | if isInitialism(s) {
38 | return true
39 | }
40 |
41 | words := split(s)
42 | wordsCnt := wordsCount(words)
43 |
44 | if wordsCnt["errors"] != 1 && wordsCnt["error"] != 1 {
45 | return false
46 | }
47 |
48 | lastWord := words[len(words)-1]
49 | return lastWord == "errors" || lastWord == "error"
50 | }
51 |
52 | func isValidErrorVarName(s string) bool {
53 | if isInitialism(s) {
54 | return true
55 | }
56 |
57 | words := split(s)
58 | wordsCnt := wordsCount(words)
59 |
60 | if wordsCnt["err"] != 1 {
61 | return false
62 | }
63 | return words[0] == "err"
64 | }
65 |
66 | func isInitialism(s string) bool {
67 | return strings.ToLower(s) == s || strings.ToUpper(s) == s
68 | }
69 |
70 | func split(s string) []string {
71 | var words []string
72 | ss := []rune(s)
73 |
74 | var b strings.Builder
75 | b.WriteRune(ss[0])
76 |
77 | for _, r := range ss[1:] {
78 | if unicode.IsUpper(r) {
79 | words = append(words, strings.ToLower(b.String()))
80 | b.Reset()
81 | }
82 | b.WriteRune(r)
83 | }
84 |
85 | words = append(words, strings.ToLower(b.String()))
86 | return words
87 | }
88 |
89 | func wordsCount(w []string) map[string]int {
90 | result := make(map[string]int, len(w))
91 | for _, ww := range w {
92 | result[ww]++
93 | }
94 | return result
95 | }
96 |
--------------------------------------------------------------------------------
/pkg/analyzer/facts_test.go:
--------------------------------------------------------------------------------
1 | package analyzer
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_isValidErrorTypeName_validTypes(t *testing.T) {
8 | for _, tt := range []string{
9 | "AddrError",
10 | "AmbiguousImportError",
11 | "BuildError",
12 | "BuildListError",
13 | "CertificateInvalidError",
14 | "CheckEqualError",
15 | "CheckError",
16 | "ConstraintError",
17 | "ConstraintViolationError",
18 | "CustomError",
19 | "CycleInRequiresGraphError",
20 | "DLLError",
21 | "DNSConfigError",
22 | "DNSError",
23 | "DeadlineExceededError",
24 | "DecodeError",
25 | "DecodingError",
26 | "DirectImportFromImplicitDependencyError",
27 | "DownloadDirPartialError",
28 | "EmbedError",
29 | "Error",
30 | "ERROR",
31 | "ExecError",
32 | "ExitError",
33 | "FileError",
34 | "FlagNotDefinedError",
35 | "FormatError",
36 | "HTTPError",
37 | "HostnameError",
38 | "ImportMismatchError",
39 | "ImportMissingError",
40 | "ImportMissingSumError",
41 | "InvalidPathError",
42 | "InvalidSignatureError",
43 | "InvalidUTF8Error",
44 | "InvalidUnmarshalError",
45 | "InvalidVersionError",
46 | "LinkError",
47 | "MarshalerError",
48 | "MatchError",
49 | "ModuleError",
50 | "ModuleRetractedError",
51 | "MultiplePackageError",
52 | "MyError",
53 | "NoGoError",
54 | "NoMatchingVersionError",
55 | "NoPatchBaseError",
56 | "NonFlagError",
57 | "NumError",
58 | "OpError",
59 | "PackageError",
60 | "PackageNotInModuleError",
61 | "ParseError",
62 | "PathError",
63 | "ProtocolError",
64 | "PtyError",
65 | "QueryMatchesMainModuleError",
66 | "QueryMatchesPackagesInMainModuleError",
67 | "ReadError",
68 | "RecordHeaderError",
69 | "ReferralError",
70 | "RunError",
71 | "StructuralError",
72 | "SyntaxError",
73 | "SyscallError",
74 | "SystemRootsError",
75 | "TagPathError",
76 | "TypeAssertionError",
77 | "UnknownAuthorityError",
78 | "UnknownLineError",
79 | "UnknownRevisionError",
80 | "UnknownVerifierError",
81 | "UnmarshalFieldError",
82 | "UnmarshalTypeError",
83 | "UnsupportedTypeError",
84 | "UnsupportedValueError",
85 | "UnverifiedNoteError",
86 | "VCSError",
87 | "ValueError",
88 | "WildcardInFirstElementError",
89 | "WriteError",
90 | "alwaysError",
91 | "ambiguousVerifierError",
92 | "badPathError",
93 | "boundsError",
94 | "cgoError",
95 | "conflictError",
96 | "deadlineExceededError",
97 | "entryNotFoundError",
98 | "err",
99 | "error",
100 | "excludedError",
101 | "formatError",
102 | "goModDirtyError",
103 | "gobError",
104 | "http2GoAwayError",
105 | "http2StreamError",
106 | "http2connError",
107 | "http2goAwayFlowError",
108 | "http2httpError",
109 | "http2noCachedConnError",
110 | "http2resAndError",
111 | "httpError",
112 | "importError",
113 | "invalidImportError",
114 | "invalidUnmarshalError",
115 | "issue22091Error",
116 | "jsonError",
117 | "labelError",
118 | "localError",
119 | "lookupDisabledError",
120 | "mainPackageError",
121 | "myError",
122 | "nestedError",
123 | "noCommitsError",
124 | "noWrapError",
125 | "notExistError",
126 | "nothingWrittenError",
127 | "onceError",
128 | "osError",
129 | "panicError",
130 | "pasteIndicatorError",
131 | "permanentError",
132 | "queryDisabledError",
133 | "requestBodyReadError",
134 | "responseAndError",
135 | "retractionLoadingError",
136 | "scanError",
137 | "statusError",
138 | "stringError",
139 | "sumMissingError",
140 | "summaryError",
141 | "temporaryError",
142 | "testError",
143 | "timeoutError",
144 | "timeoutTemporaryError",
145 | "tlsHandshakeTimeoutError",
146 | "transportReadFromServerError",
147 | "unexportedCustomError",
148 | "unexportedError",
149 | "unsupportedTEError",
150 | "wantedError",
151 | "withStackError",
152 | "withTimeoutError",
153 | "wrapError",
154 | "writeError",
155 | "writerWithReadFromError",
156 | "zipError",
157 | } {
158 | if !isValidErrorTypeName(tt) {
159 | t.Errorf("%q must be valid error type name", tt)
160 | }
161 | }
162 | }
163 |
164 | func Test_isValidErrorTypeName_invalidTypes(t *testing.T) {
165 | for _, tt := range []string{
166 | "AmbiguousImportErr",
167 | "BuildErr",
168 | "CertificateInvalidErr",
169 | "CheckEqualErrorErr",
170 | "CheckEqualErrorError",
171 | "Err",
172 | "ErrorBuildList",
173 | "ErrorBuildListError",
174 | "errWithStack",
175 | "errZIP",
176 | "nestedErr",
177 | "withStackErr",
178 | "withStackERR",
179 | "withStackERROR",
180 | } {
181 | if isValidErrorTypeName(tt) {
182 | t.Errorf("%q must be invalid error type name", tt)
183 | }
184 | }
185 | }
186 |
187 | func Test_isValidErrorArrayTypeName(t *testing.T) {
188 | for _, tt := range []string{
189 | "ValidationErrors",
190 | "validationErrors",
191 | "ERRORS",
192 | "errors",
193 | "IncompatiblePairError",
194 | "multiError",
195 | } {
196 | if !isValidErrorArrayTypeName(tt) {
197 | t.Errorf("%q must be valid error array type name", tt)
198 | }
199 | }
200 |
201 | for _, tt := range []string{
202 | "ErrorsFromValidation",
203 | "errorsFromValidation",
204 | "ErrorMulti",
205 | "ErrorsOfProcessing",
206 | "processingErrs",
207 | "ValidationErr",
208 | } {
209 | if isValidErrorArrayTypeName(tt) {
210 | t.Errorf("%q must be invalid error array type name", tt)
211 | }
212 | }
213 | }
214 |
215 | func Test_isValidErrorVarName_validVars(t *testing.T) {
216 | for _, tt := range []string{
217 | "EOF",
218 | "ErrAbortHandler",
219 | "ErrAdvanceTooFar",
220 | "ErrAlgorithm",
221 | "ErrBadConn",
222 | "ErrBadName",
223 | "ErrBadPattern",
224 | "ErrBadReadCount",
225 | "ErrBadStat",
226 | "ErrBareQuote",
227 | "ErrBodyNotAllowed",
228 | "ErrBodyReadAfterClose",
229 | "ErrBufferFull",
230 | "ErrChecksum",
231 | "ErrClosed",
232 | "ErrClosedPipe",
233 | "ErrConnClosed",
234 | "ErrConnDone",
235 | "ErrContentLength",
236 | "ErrDecryption",
237 | "ErrDictionary",
238 | "ErrDisallowed",
239 | "ErrEndOfSpan",
240 | "ErrExecError",
241 | "ErrExist",
242 | "ErrFieldCount",
243 | "ErrFieldTooLong",
244 | "ErrFileClosing",
245 | "ErrFinalToken",
246 | "ErrFlagTerminator",
247 | "ErrFormat",
248 | "ErrFrameTooLarge",
249 | "ErrGONOSUMDB",
250 | "ErrHandlerTimeout",
251 | "ErrHeader",
252 | "ErrHeaderNotPresent",
253 | "ErrHelp",
254 | "ErrHijacked",
255 | "ErrInvalid",
256 | "ErrInvalidHuffman",
257 | "ErrInvalidMediaParameter",
258 | "ErrInvalidMode",
259 | "ErrInvalidPublicKey",
260 | "ErrInvalidUnreadByte",
261 | "ErrInvalidUnreadRune",
262 | "ErrLength",
263 | "ErrLineTooLong",
264 | "ErrMessageTooLarge",
265 | "ErrMessageTooLong",
266 | "ErrMissingFile",
267 | "ErrNegativeAdvance",
268 | "ErrNegativeCount",
269 | "ErrNoCookie",
270 | "ErrNoDeadline",
271 | "ErrNoLocation",
272 | "ErrNoModRoot",
273 | "ErrNoProgress",
274 | "ErrNoRows",
275 | "ErrNoSymbols",
276 | "ErrNoTrustSettings",
277 | "ErrNotExist",
278 | "ErrNotFound",
279 | "ErrNotMangledName",
280 | "ErrNotPollable",
281 | "ErrNotStarted",
282 | "ErrNotSupported",
283 | "ErrPermission",
284 | "ErrProcessDone",
285 | "ErrPushLimitReached",
286 | "ErrQuote",
287 | "ErrRange",
288 | "ErrRecursivePush",
289 | "ErrRemoveArgument",
290 | "ErrRequestAborted",
291 | "ErrSectionDone",
292 | "ErrSecurity",
293 | "ErrServerClosed",
294 | "ErrShortBuffer",
295 | "ErrShortDst",
296 | "ErrShortSrc",
297 | "ErrShortStat",
298 | "ErrShortWrite",
299 | "ErrShutdown",
300 | "ErrSkip",
301 | "ErrSkipAltProtocol",
302 | "ErrStringLength",
303 | "ErrSyntax",
304 | "ErrTimeout",
305 | "ErrTooLarge",
306 | "ErrTooLong",
307 | "ErrTooManyErrors",
308 | "ErrTrailingComma",
309 | "ErrTruncated",
310 | "ErrTxDone",
311 | "ErrUnexpectedEOF",
312 | "ErrUnexpectedType",
313 | "ErrUnknownPC",
314 | "ErrUnrecognized",
315 | "ErrUnsupportedAlgorithm",
316 | "ErrUseLastResponse",
317 | "ErrVerification",
318 | "ErrWriteAfterClose",
319 | "ErrWriteAfterFlush",
320 | "ErrWriteConflict",
321 | "ErrWriteToConnected",
322 | "ErrWriteTooLong",
323 | "errBad",
324 | "errBadCgo",
325 | "errBadComparison",
326 | "errBadComparisonType",
327 | "errBadCount",
328 | "errBadELF",
329 | "errBadPixel",
330 | "errBadTestInputs",
331 | "errBadType",
332 | "errBadUint",
333 | "errBaseLen",
334 | "errCalcLen",
335 | "errCallerOwnsConn",
336 | "errCanceled",
337 | "errCannotMarshalDNSMessage",
338 | "errCannotRewind",
339 | "errCannotUnmarshalDNSMessage",
340 | "errClientConnClosed",
341 | "errClientConnGotGoAway",
342 | "errClientConnUnusable",
343 | "errClientDisconnected",
344 | "errClientKeyExchange",
345 | "errCloseConn",
346 | "errCloseIdle",
347 | "errCloseIdleConns",
348 | "errClosed",
349 | "errClosedBody",
350 | "errClosedPipeWrite",
351 | "errClosedResponseBody",
352 | "errComplex",
353 | "errCompressedSRV",
354 | "errConnBroken",
355 | "errCorruptArchive",
356 | "errCorruptObject",
357 | "errDBClosed",
358 | "errDepStreamID",
359 | "errDial",
360 | "errDirectoryNotFound",
361 | "errDiscardedBytes",
362 | "errEarlyCloseWrite",
363 | "errFail",
364 | "errFake",
365 | "errFakeConnSessionDirty",
366 | "errFakeRoundTrip",
367 | "errFileTooNew",
368 | "errGoBuildWithoutBuild",
369 | "errGoModCase",
370 | "errHandlerComplete",
371 | "errHandlerPanicked",
372 | "errHgArchivalTxt",
373 | "errIO",
374 | "errIdleConnTimeout",
375 | "errIllegalDomain",
376 | "errInconsistentByteCount",
377 | "errInternal",
378 | "errInvalSep",
379 | "errInvalidAddr",
380 | "errInvalidDNSResponse",
381 | "errInvalidDelim",
382 | "errInvalidInterface",
383 | "errInvalidInterfaceIndex",
384 | "errInvalidInterfaceName",
385 | "errInvalidMessage",
386 | "errInvalidPtr",
387 | "errInvalidSigner",
388 | "errInvalidWord",
389 | "errInvalidWrite",
390 | "errKeepAlivesDisabled",
391 | "errLameReferral",
392 | "errLeadingInt",
393 | "errLocation",
394 | "errLongExtra",
395 | "errLongName",
396 | "errMain",
397 | "errMalformedDomain",
398 | "errMalformedNote",
399 | "errMalformedRecord",
400 | "errMalformedTree",
401 | "errMessageMismatch",
402 | "errMessageTooShort",
403 | "errMissData",
404 | "errMissing",
405 | "errMissingAddress",
406 | "errMissingHost",
407 | "errMissingParams",
408 | "errMissingReadDir",
409 | "errMissingSeek",
410 | "errMissingU8Prefix",
411 | "errMixPseudoHeaderTypes",
412 | "errMultipleGoBuild",
413 | "errNUL",
414 | "errNeedMore",
415 | "errNegativeRead",
416 | "errNegativeWrite",
417 | "errNilPanicOrGoexit",
418 | "errNilPtr",
419 | "errNilResouceBody",
420 | "errNoAnswerFromDNSServer",
421 | "errNoAvailableAddress",
422 | "errNoAvailableInterface",
423 | "errNoBody",
424 | "errNoBuildID",
425 | "errNoCertificates",
426 | "errNoChange",
427 | "errNoComparison",
428 | "errNoDigits",
429 | "errNoFallocate",
430 | "errNoHostname",
431 | "errNoModules",
432 | "errNoOverlap",
433 | "errNoRows",
434 | "errNoSuchHost",
435 | "errNoSuchInterface",
436 | "errNoSuchMulticastInterface",
437 | "errNoSuitableAddress",
438 | "errNonCanonicalName",
439 | "errNotAbsolute",
440 | "errNotConstraint",
441 | "errNotDir",
442 | "errNotEnough",
443 | "errNotObject",
444 | "errNotParsed",
445 | "errNotPermitted",
446 | "errNotRegular",
447 | "errOffline",
448 | "errOffset",
449 | "errOpen",
450 | "errOutOfCodes",
451 | "errPadBytes",
452 | "errPadLength",
453 | "errParse",
454 | "errPathNotClean",
455 | "errPathNotRelative",
456 | "errPatternHasSeparator",
457 | "errPkgIsBuiltin",
458 | "errPkgIsGorootSrc",
459 | "errPrefaceTimeout",
460 | "errPrintedOutput",
461 | "errProofFailed",
462 | "errPseudoAfterRegular",
463 | "errPseudoSyntax",
464 | "errPublicExponentLarge",
465 | "errPublicExponentSmall",
466 | "errPublicModulus",
467 | "errRange",
468 | "errRawToken",
469 | "errReadEmpty",
470 | "errReadLoopExiting",
471 | "errReadOnClosedResBody",
472 | "errRemoteOriginNotFound",
473 | "errReqBodyTooLong",
474 | "errRequestCanceled",
475 | "errRequestCanceledConn",
476 | "errRequestHeaderListSize",
477 | "errResTooLong",
478 | "errReserved",
479 | "errResourceLen",
480 | "errResponseHeaderListSize",
481 | "errRevQuery",
482 | "errRowsClosed",
483 | "errSeeker",
484 | "errSegTooLong",
485 | "errSegmentSelector",
486 | "errServerClosedIdle",
487 | "errServerKeyExchange",
488 | "errServerMisbehaving",
489 | "errServerTemporarilyMisbehaving",
490 | "errShortBuffer",
491 | "errShortInternal",
492 | "errShutdown",
493 | "errSignerAlg",
494 | "errSignerHash",
495 | "errSignerID",
496 | "errStopReqBodyWrite",
497 | "errStopReqBodyWriteAndCancel",
498 | "errStreamClosed",
499 | "errStreamID",
500 | "errStringTooLong",
501 | "errSubmoduleDir",
502 | "errSubmoduleFile",
503 | "errSymlink",
504 | "errSyntax",
505 | "errTagKeySyntax",
506 | "errTagSpace",
507 | "errTagSyntax",
508 | "errTagValueSpace",
509 | "errTagValueSyntax",
510 | "errTestScanWrap",
511 | "errTooBig",
512 | "errTooLarge",
513 | "errTooManyAdditionals",
514 | "errTooManyAnswers",
515 | "errTooManyAuthorities",
516 | "errTooManyIdle",
517 | "errTooManyIdleHost",
518 | "errTooManyPtr",
519 | "errTooManyQuestions",
520 | "errTooMuch",
521 | "errTrailerEOF",
522 | "errTruncatedArchive",
523 | "errUnknownSite",
524 | "errUnreadByte",
525 | "errUnrefData",
526 | "errUnsupportedMessage",
527 | "errVCS",
528 | "errValue",
529 | "errVendored",
530 | "errVerifierAlg",
531 | "errVerifierHash",
532 | "errVerifierID",
533 | "errVerifyMode",
534 | "errVersionChange",
535 | "errWhence",
536 | "errWriteAtInAppendMode",
537 | "errWriteHole",
538 | "errZeroParam",
539 | "errZeroSegLen",
540 | } {
541 | if !isValidErrorVarName(tt) {
542 | t.Errorf("%q must be valid error var name", tt)
543 | }
544 | }
545 | }
546 |
547 | func Test_isValidErrorVarName_invalidVars(t *testing.T) {
548 | for _, tt := range []string{
549 | "Canceled",
550 | "ERRORExec",
551 | "ERRExec",
552 | "ErrExecErr",
553 | "ErrorExec",
554 | "IncorrectPasswordError",
555 | "SkipDir",
556 | "alwaysError",
557 | "atoiError",
558 | "badData",
559 | "badErr",
560 | "boolError",
561 | "brokenConnErr",
562 | "checkErr",
563 | "complexError",
564 | "fakeErr",
565 | "isInternal",
566 | "isNotExistErr",
567 | "myError",
568 | "outErr",
569 | "pingError",
570 | "sentinelErr",
571 | "someErr",
572 | "statErr",
573 | "testError",
574 | "timeoutProtoErr",
575 | "viewCloseError",
576 | "viewERR",
577 | "viewERROR",
578 | "viewError",
579 | } {
580 | if isValidErrorVarName(tt) {
581 | t.Errorf("%q must be invalid error var name", tt)
582 | }
583 | }
584 | }
585 |
586 | func Test_isInitialism(t *testing.T) {
587 | for _, tt := range []struct {
588 | in string
589 | expected bool
590 | }{
591 | {in: "E", expected: true},
592 | {in: "e", expected: true},
593 | {in: "EOF", expected: true},
594 | {in: "eof", expected: true},
595 | {in: "ERPCMISMATCH", expected: true},
596 | {in: "EndOfFile", expected: false},
597 | {in: "withStackError", expected: false},
598 | {in: "badData", expected: false},
599 | } {
600 | t.Run(tt.in, func(t *testing.T) {
601 | if isInitialism(tt.in) != tt.expected {
602 | t.Fail()
603 | }
604 | })
605 | }
606 | }
607 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/error_types.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | type NotErrorType struct{}
4 |
5 | func (t NotErrorType) Set() {}
6 | func (t NotErrorType) Get() {}
7 |
8 | type DNSConfigError struct{}
9 |
10 | func (D DNSConfigError) Error() string { return "DNS config error" }
11 |
12 | type someTypeWithoutPtr struct{} // want "the error type name `someTypeWithoutPtr` should conform to the `xxxError` format"
13 | func (s someTypeWithoutPtr) Error() string { return "someTypeWithoutPtr" }
14 |
15 | type SomeTypeWithoutPtr struct{} // want "the error type name `SomeTypeWithoutPtr` should conform to the `XxxError` format"
16 | func (s SomeTypeWithoutPtr) Error() string { return "SomeTypeWithoutPtr" }
17 |
18 | type someTypeWithPtr struct{} // want "the error type name `someTypeWithPtr` should conform to the `xxxError` format"
19 | func (s *someTypeWithPtr) Error() string { return "someTypeWithPtr" }
20 |
21 | type (
22 | SomeTypeAlias = SomeTypeWithPtr // want "the error type name `SomeTypeAlias` should conform to the `XxxError` format"
23 |
24 | SomeTypeWithPtr struct{} // want "the error type name `SomeTypeWithPtr` should conform to the `XxxError` format"
25 | )
26 |
27 | func (s *SomeTypeWithPtr) Error() string { return "SomeTypeWithPtr" }
28 |
29 | type timeoutErr struct { // want "the error type name `timeoutErr` should conform to the `xxxError` format"
30 | error
31 | }
32 |
33 | type DeadlineErr struct { // want "the error type name `DeadlineErr` should conform to the `XxxError` format"
34 | timeoutErr
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/error_types_arrays.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | import "strings"
4 |
5 | type ValidationErrors []string
6 |
7 | func (ve ValidationErrors) Error() string { return strings.Join(ve, "\n") }
8 |
9 | type validationErrors []string
10 |
11 | func (ve validationErrors) Error() string { return strings.Join(ve, "\n") }
12 |
13 | type TenErrors [10]string
14 |
15 | func (te TenErrors) Error() string { return strings.Join(te[:], "\n") }
16 |
17 | type tenErrors [10]string
18 |
19 | func (te tenErrors) Error() string { return strings.Join(te[:], "\n") }
20 |
21 | type MultiErr []error // want "the error type name `MultiErr` should conform to the `XxxErrors` or `XxxError` format"
22 | func (me MultiErr) Error() string { return "" }
23 |
24 | type multiErr []error // want "the error type name `multiErr` should conform to the `xxxErrors` or `xxxError` format"
25 | func (me multiErr) Error() string { return "" }
26 |
27 | type Twoerr [2]error // want "the error type name `Twoerr` should conform to the `XxxErrors` or `XxxError` format"
28 | func (te Twoerr) Error() string { return te[0].Error() + "\n" + te[1].Error() }
29 |
30 | type twoErrorss [2]error // want "the error type name `twoErrorss` should conform to the `xxxErrors` or `xxxError` format"
31 | func (te twoErrorss) Error() string { return te[0].Error() + "\n" + te[1].Error() }
32 |
33 | type MultiError []error
34 |
35 | func (me MultiError) Error() string { return "" }
36 |
37 | type multiError []error
38 |
39 | func (me multiError) Error() string { return "" }
40 |
41 | type TwoError [2]error
42 |
43 | func (te TwoError) Error() string { return te[0].Error() + "\n" + te[1].Error() }
44 |
45 | type twoError [2]error
46 |
47 | func (te twoError) Error() string { return te[0].Error() + "\n" + te[1].Error() }
48 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/sentinel.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | var (
9 | EOF = errors.New("end of file")
10 | ErrEndOfFile = errors.New("end of file")
11 | errEndOfFile = errors.New("end of file")
12 |
13 | EndOfFileError = errors.New("end of file") // want "the sentinel error name `EndOfFileError` should conform to the `ErrXxx` format"
14 | ErrorEndOfFile = errors.New("end of file") // want "the sentinel error name `ErrorEndOfFile` should conform to the `ErrXxx` format"
15 | EndOfFileErr = errors.New("end of file") // want "the sentinel error name `EndOfFileErr` should conform to the `ErrXxx` format"
16 | endOfFileError = errors.New("end of file") // want "the sentinel error name `endOfFileError` should conform to the `errXxx` format"
17 | errorEndOfFile = errors.New("end of file") // want "the sentinel error name `errorEndOfFile` should conform to the `errXxx` format"
18 | )
19 |
20 | const maxSize = 256
21 |
22 | var (
23 | ErrOutOfSize = fmt.Errorf("out of size (max %d)", maxSize)
24 | errOutOfSize = fmt.Errorf("out of size (max %d)", maxSize)
25 |
26 | OutOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `OutOfSizeError` should conform to the `ErrXxx` format"
27 | outOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `outOfSizeError` should conform to the `errXxx` format"
28 | )
29 |
30 | func errInsideFuncIsNotSentinel() error {
31 | var lastErr error
32 | return lastErr
33 | }
34 |
35 | var _ = func() {
36 | run("", func() {
37 | var orderErr error
38 | _ = orderErr
39 | })
40 | }
41 |
42 | func run(_ string, _ func()) {}
43 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/sentinel_from_constants.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | type constError string
4 |
5 | func (e constError) Error() string {
6 | return string(e)
7 | }
8 |
9 | const (
10 | ErrTooManyErrors constError = "too many errors found"
11 |
12 | ErrorTooMany1 constError = "too many errors found" // want "the sentinel error name `ErrorTooMany1` should conform to the `ErrXxx` format"
13 | ErrorTooMany2 = constError("too many errors found") // want "the sentinel error name `ErrorTooMany2` should conform to the `ErrXxx` format"
14 | )
15 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/sentinel_from_funcs.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | import "io"
4 |
5 | var (
6 | ErrA = newSomeTypeWithPtr()
7 | ErrB = newSomeTypeWithPtr2()
8 | ErrC = newSomeTypeWithoutPtr()
9 | ErrD = newSomeTypeWithoutPtr2()
10 | ErrE = new(SomeTypeWithPtr)
11 | ErrF = &SomeTypeWithPtr{}
12 | ErrG = SomeTypeWithoutPtr{}
13 |
14 | AErr = newSomeTypeWithPtr() // want "the sentinel error name `AErr` should conform to the `ErrXxx` format"
15 | BErr = newSomeTypeWithPtr2() // want "the sentinel error name `BErr` should conform to the `ErrXxx` format"
16 | CErr = newSomeTypeWithoutPtr() // want "the sentinel error name `CErr` should conform to the `ErrXxx` format"
17 | DErr = newSomeTypeWithoutPtr2() // want "the sentinel error name `DErr` should conform to the `ErrXxx` format"
18 | EErr = new(SomeTypeWithPtr) // want "the sentinel error name `EErr` should conform to the `ErrXxx` format"
19 | FErr = &SomeTypeWithPtr{} // want "the sentinel error name `FErr` should conform to the `ErrXxx` format"
20 | GErr = SomeTypeWithoutPtr{} // want "the sentinel error name `GErr` should conform to the `ErrXxx` format"
21 |
22 | AErrr error = newSomeTypeWithPtr2() // want "the sentinel error name `AErrr` should conform to the `ErrXxx` format"
23 | BErrr error = newSomeTypeWithoutPtr() // want "the sentinel error name `BErrr` should conform to the `ErrXxx` format"
24 | CErrr error = new(SomeTypeWithPtr) // want "the sentinel error name `CErrr` should conform to the `ErrXxx` format"
25 | DErrr error = &SomeTypeWithPtr{} // want "the sentinel error name `DErrr` should conform to the `ErrXxx` format"
26 | EErrr error = SomeTypeWithoutPtr{} // want "the sentinel error name `EErrr` should conform to the `ErrXxx` format"
27 |
28 | ErrByAnonymousFunc = func() error { return nil }
29 | ByAnonymousFuncErr = func() error { return io.EOF }() // want "the sentinel error name `ByAnonymousFuncErr` should conform to the `ErrXxx` format"
30 | )
31 |
32 | var (
33 | InitializedLaterError error // want "the sentinel error name `InitializedLaterError` should conform to the `ErrXxx` format"
34 | InitializedLaterImplicitError SomeTypeWithoutPtr // want "the sentinel error name `InitializedLaterImplicitError` should conform to the `ErrXxx` format"
35 | InitializedLaterImplicitPtrError *SomeTypeWithPtr // want "the sentinel error name `InitializedLaterImplicitPtrError` should conform to the `ErrXxx` format"
36 | )
37 |
38 | func init() {
39 | InitializedLaterError = newSomeTypeWithPtr()
40 | InitializedLaterImplicitError = newSomeTypeWithoutPtr()
41 | InitializedLaterImplicitPtrError = newSomeTypeWithPtr2()
42 | }
43 |
44 | func newSomeTypeWithPtr() error {
45 | return new(SomeTypeWithPtr)
46 | }
47 |
48 | func newSomeTypeWithPtr2() *SomeTypeWithPtr {
49 | return nil
50 | }
51 |
52 | func newSomeTypeWithoutPtr() SomeTypeWithoutPtr {
53 | return SomeTypeWithoutPtr{}
54 | }
55 |
56 | func newSomeTypeWithoutPtr2() error {
57 | return SomeTypeWithoutPtr{}
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/sentinel_from_pkg_alias.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | import (
4 | stderrors "errors"
5 | stdfmt "fmt"
6 | )
7 |
8 | var ErrAlias = stderrors.New("err from alias")
9 | var AliasErr = stderrors.New("err from alias") // want "the sentinel error name `AliasErr` should conform to the `ErrXxx` format"
10 |
11 | var ErrOutOfSize2 = stdfmt.Errorf("out of size (max %d)", maxSize)
12 | var OutOfSizeError2 = stdfmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `OutOfSizeError2` should conform to the `ErrXxx` format"
13 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/regular/sentinel_from_types.go:
--------------------------------------------------------------------------------
1 | package regular
2 |
3 | import "net"
4 |
5 | var (
6 | InvalidAddrError = new(net.AddrError) // want "the sentinel error name `InvalidAddrError` should conform to the `ErrXxx` format"
7 | InvalidAddrErr error = new(net.AddrError) // want "the sentinel error name `InvalidAddrErr` should conform to the `ErrXxx` format"
8 | NotErr = new(NotErrorType)
9 |
10 | Aa = new(someTypeWithPtr) // want "the sentinel error name `Aa` should conform to the `ErrXxx` format"
11 | Bb = someTypeWithoutPtr{} // want "the sentinel error name `Bb` should conform to the `ErrXxx` format"
12 | Bbb = someTypeWithPtr{}
13 |
14 | cC error = new(someTypeWithPtr) // want "the sentinel error name `cC` should conform to the `errXxx` format"
15 | dD error = someTypeWithoutPtr{} // want "the sentinel error name `dD` should conform to the `errXxx` format"
16 |
17 | Alias = InvalidAddrErr // want "the sentinel error name `Alias` should conform to the `ErrXxx` format"
18 | )
19 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/unusual/errortype/user_error_type.go:
--------------------------------------------------------------------------------
1 | package errortype
2 |
3 | type error string
4 |
5 | var EndOfFileError = error("I am not error")
6 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/unusual/generics/generic_types.go:
--------------------------------------------------------------------------------
1 | package generics
2 |
3 | type NotErrorGeneric[T float64 | int] struct {
4 | Limit T
5 | }
6 |
7 | type SomeError[T ~string] struct{ Code T }
8 |
9 | func (e SomeError[T]) Error() string { return string(e.Code) }
10 |
11 | type SomePtrError[T ~string] struct{ Code T }
12 |
13 | func (e *SomePtrError[T]) Error() string { return string(e.Code) }
14 |
15 | type someErr[T ~string] struct{ Code T } // want "the error type name `someErr` should conform to the `xxxError` format"
16 | func (e someErr[T]) Error() string { return string(e.Code) }
17 |
18 | type SomePtrErr[T ~string] struct{ Code T } // want "the error type name `SomePtrErr` should conform to the `XxxError` format"
19 | func (e *SomePtrErr[T]) Error() string { return string(e.Code) }
20 |
21 | var (
22 | ErrOK = &SomePtrError[string]{Code: "200 OK"}
23 | okErr = &SomePtrError[string]{Code: "200 OK"} // want "the sentinel error name `okErr` should conform to the `errXxx` format"
24 |
25 | ErrNotFound = SomeError[string]{Code: "Not Found"}
26 | NotFoundErr = SomeError[string]{Code: "Not Found"} // want "the sentinel error name `NotFoundErr` should conform to the `ErrXxx` format"
27 |
28 | statusCodeError = new(SomePtrError[string]) // want "the sentinel error name `statusCodeError` should conform to the `errXxx` format"
29 | ExplicitError error = new(SomePtrError[string]) // want "the sentinel error name `ExplicitError` should conform to the `ErrXxx` format"
30 | )
31 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/unusual/generics/issue24.go:
--------------------------------------------------------------------------------
1 | package generics
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "reflect"
8 | )
9 |
10 | type (
11 | Req any
12 | Resp any
13 | )
14 |
15 | type timeoutErr[REQ Req, RESP Resp] struct { // want "the error type name `timeoutErr` should conform to the `xxxError` format"
16 | err error
17 | sending bool
18 | }
19 |
20 | func (e *timeoutErr[REQ, RESP]) Error() string {
21 | var req REQ
22 | var resp RESP
23 |
24 | direction := "sending"
25 | if !e.sending {
26 | direction = "receiving"
27 | }
28 |
29 | return fmt.Sprintf("deferred call %T->%T timeout %s: %s",
30 | reflect.TypeOf(req), reflect.TypeOf(resp), direction, e.err.Error())
31 | }
32 |
33 | func (e *timeoutErr[REQ, RESP]) Unwrap() error {
34 | return e.err
35 | }
36 |
37 | type TimeoutError[REQ Req, RESP Resp] struct{} //
38 | func (TimeoutError[REQ, RESP]) Error() string { return "timeouted" }
39 |
40 | type ValErr[A, B, C, D, E, F any] struct{} // want "the error type name `ValErr` should conform to the `XxxError` format"
41 | func (ValErr[A, B, C, D, E, F]) Error() string { return "boom!" }
42 |
43 | var (
44 | ErrTimeout error = &timeoutErr[*http.Request, *http.Response]{err: context.DeadlineExceeded, sending: false}
45 | tErr error = &timeoutErr[string, string]{err: context.DeadlineExceeded, sending: true} // want "the sentinel error name `tErr` should conform to the `errXxx` format"
46 | )
47 |
--------------------------------------------------------------------------------
/pkg/analyzer/testdata/src/unusual/newfunc/user_new_func.go:
--------------------------------------------------------------------------------
1 | package newfunc
2 |
3 | var FromUserNewError = new() // want "the sentinel error name `FromUserNewError` should conform to the `ErrXxx` format"
4 |
5 | func new() error {
6 | return nil
7 | }
8 |
--------------------------------------------------------------------------------